fiddler-client 2.5.0.dev1__py3-none-any.whl → 2.5.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fiddler/_version.py +1 -1
- fiddler/core_objects.py +18 -5
- fiddler/schema/alert.py +3 -3
- fiddler/schemas/custom_features.py +31 -1
- fiddler3/__init__.py +121 -3
- fiddler3/configs.py +3 -0
- fiddler3/constants/model.py +9 -0
- fiddler3/entities/__init__.py +0 -10
- fiddler3/entities/alert_rule.py +79 -2
- fiddler3/entities/baseline.py +21 -15
- fiddler3/entities/dataset.py +1 -1
- fiddler3/entities/events.py +68 -52
- fiddler3/entities/{files.py → file.py} +7 -15
- fiddler3/entities/model.py +81 -13
- fiddler3/entities/model_artifact.py +5 -5
- fiddler3/entities/model_deployment.py +12 -1
- fiddler3/entities/project.py +1 -1
- fiddler3/entities/{model_surrogate.py → surrogate.py} +27 -29
- fiddler3/entities/xai.py +8 -9
- fiddler3/libs/http_client.py +1 -1
- fiddler3/schemas/__init__.py +0 -9
- fiddler3/schemas/alert_rule.py +7 -0
- fiddler3/schemas/custom_features.py +34 -12
- fiddler3/schemas/model_deployment.py +5 -4
- fiddler3/schemas/webhook.py +0 -3
- fiddler3/tests/apis/test_alert_rule.py +71 -1
- fiddler3/tests/apis/test_events.py +41 -0
- fiddler3/tests/apis/test_files.py +1 -1
- fiddler3/tests/apis/test_model.py +5 -1
- fiddler3/tests/apis/test_project.py +1 -1
- fiddler3/tests/apis/test_webhook.py +0 -23
- fiddler3/utils/helpers.py +20 -0
- {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/METADATA +15 -5
- {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/RECORD +38 -39
- fiddler3/entities/helpers.py +0 -21
- /fiddler3/schemas/{files.py → file.py} +0 -0
- {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/LICENSE.txt +0 -0
- {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/WHEEL +0 -0
- {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/top_level.txt +0 -0
fiddler/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '2.5.0.
|
|
1
|
+
__version__ = '2.5.0.dev2'
|
fiddler/core_objects.py
CHANGED
|
@@ -1295,9 +1295,7 @@ class ModelInfo:
|
|
|
1295
1295
|
elif isinstance(feature, VectorFeature):
|
|
1296
1296
|
self.validate_vector_cf(feature, valid_vector_cols)
|
|
1297
1297
|
elif isinstance(feature, Enrichment):
|
|
1298
|
-
self.validate_enrichment_cf(
|
|
1299
|
-
feature, available_cols, valid_cf_names
|
|
1300
|
-
)
|
|
1298
|
+
self.validate_enrichment_cf(feature, available_cols, valid_cf_names)
|
|
1301
1299
|
|
|
1302
1300
|
def validate_enrichment_cf(
|
|
1303
1301
|
self,
|
|
@@ -1326,6 +1324,17 @@ class ModelInfo:
|
|
|
1326
1324
|
valid_vector_cols: Iterable[str],
|
|
1327
1325
|
valid_enrichment_names: Iterable[str],
|
|
1328
1326
|
) -> None:
|
|
1327
|
+
if isinstance(feature, TextEmbedding):
|
|
1328
|
+
if not feature.n_tags:
|
|
1329
|
+
raise ValueError(
|
|
1330
|
+
f'{feature.name} ({feature.__class__.__name__}) must specify a number of tags.'
|
|
1331
|
+
)
|
|
1332
|
+
|
|
1333
|
+
if feature.n_tags < 0:
|
|
1334
|
+
raise ValueError(
|
|
1335
|
+
f'{feature.name} ({feature.__class__.__name__}) must specify a non-negative number of tags.'
|
|
1336
|
+
)
|
|
1337
|
+
|
|
1329
1338
|
if feature.column not in valid_vector_cols:
|
|
1330
1339
|
# we require either a column or source_column
|
|
1331
1340
|
if not feature.source_column:
|
|
@@ -1342,13 +1351,17 @@ class ModelInfo:
|
|
|
1342
1351
|
f"Column/Enrichment'{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
|
|
1343
1352
|
)
|
|
1344
1353
|
|
|
1345
|
-
def validate_vector_cf(
|
|
1354
|
+
def validate_vector_cf(
|
|
1355
|
+
self, feature: VectorFeature, valid_vector_cols: Iterable[str]
|
|
1356
|
+
) -> None:
|
|
1346
1357
|
if feature.column not in valid_vector_cols:
|
|
1347
1358
|
raise ValueError(
|
|
1348
1359
|
f"Column '{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
|
|
1349
1360
|
)
|
|
1350
1361
|
|
|
1351
|
-
def validate_multivariate_cf(
|
|
1362
|
+
def validate_multivariate_cf(
|
|
1363
|
+
self, feature: Multivariate, valid_numeric_names: Iterable[str]
|
|
1364
|
+
) -> None:
|
|
1352
1365
|
if len(feature.columns or []) < 2:
|
|
1353
1366
|
raise ValueError(
|
|
1354
1367
|
f'{feature.name} ({feature.__class__.__name__}) must specify at least two columns.'
|
fiddler/schema/alert.py
CHANGED
|
@@ -197,7 +197,7 @@ class AlertRule(BaseDataSchema):
|
|
|
197
197
|
time_bucket: Optional[int]
|
|
198
198
|
alert_type_display_name: str
|
|
199
199
|
metric_display_name: str
|
|
200
|
-
enable_notification: bool
|
|
200
|
+
enable_notification: Optional[bool]
|
|
201
201
|
|
|
202
202
|
@root_validator(pre=True)
|
|
203
203
|
def set_feature_names(cls, values: dict) -> dict:
|
|
@@ -229,6 +229,6 @@ class TriggeredAlerts(BaseDataSchema):
|
|
|
229
229
|
message: str
|
|
230
230
|
multi_col_values: Optional[Dict[str, float]]
|
|
231
231
|
feature_name: Optional[str]
|
|
232
|
-
alert_record_main_version: int
|
|
233
|
-
alert_record_sub_version: int
|
|
232
|
+
alert_record_main_version: Optional[int]
|
|
233
|
+
alert_record_sub_version: Optional[int]
|
|
234
234
|
segment: Optional[Segment]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
from typing import Any, Dict, List, Optional, Type, TypeVar
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, Extra
|
|
4
|
+
from pydantic import BaseModel, Extra, validator
|
|
5
5
|
|
|
6
6
|
CustomFeatureTypeVar = TypeVar('CustomFeatureTypeVar', bound='CustomFeature')
|
|
7
7
|
DEFAULT_NUM_CLUSTERS = 5
|
|
@@ -22,6 +22,12 @@ class CustomFeature(BaseModel):
|
|
|
22
22
|
n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
|
|
23
23
|
centroids: Optional[List] = None
|
|
24
24
|
|
|
25
|
+
@validator('n_clusters')
|
|
26
|
+
def validate_n_clusters(cls, value: int) -> int: # noqa: N805
|
|
27
|
+
if value < 0:
|
|
28
|
+
raise ValueError('n_clusters must be greater than 0')
|
|
29
|
+
return value
|
|
30
|
+
|
|
25
31
|
class Config:
|
|
26
32
|
allow_mutation = False
|
|
27
33
|
extra = Extra.forbid
|
|
@@ -81,6 +87,12 @@ class Multivariate(CustomFeature):
|
|
|
81
87
|
columns: List[str]
|
|
82
88
|
monitor_components: bool = False
|
|
83
89
|
|
|
90
|
+
@validator('columns')
|
|
91
|
+
def validate_columns(cls, value: List[str]) -> List[str]: # noqa: N805
|
|
92
|
+
if len(value) < 2:
|
|
93
|
+
raise ValueError('Multivariate features must have at least 2 columns')
|
|
94
|
+
return value
|
|
95
|
+
|
|
84
96
|
|
|
85
97
|
class VectorFeature(CustomFeature):
|
|
86
98
|
type: CustomFeatureType = CustomFeatureType.FROM_VECTOR
|
|
@@ -92,10 +104,28 @@ class TextEmbedding(VectorFeature):
|
|
|
92
104
|
type: CustomFeatureType = CustomFeatureType.FROM_TEXT_EMBEDDING
|
|
93
105
|
n_tags: Optional[int] = DEFAULT_NUM_TAGS
|
|
94
106
|
|
|
107
|
+
@validator('source_column')
|
|
108
|
+
def validate_source_column(cls, value: str) -> str: # noqa: N805
|
|
109
|
+
if value is None:
|
|
110
|
+
raise ValueError('source_column must be specified')
|
|
111
|
+
return value
|
|
112
|
+
|
|
113
|
+
@validator('n_tags')
|
|
114
|
+
def validate_n_tags(cls, value: int) -> int: # noqa: N805
|
|
115
|
+
if value < 1:
|
|
116
|
+
raise ValueError('n_tags must be greater than 0')
|
|
117
|
+
return value
|
|
118
|
+
|
|
95
119
|
|
|
96
120
|
class ImageEmbedding(VectorFeature):
|
|
97
121
|
type: CustomFeatureType = CustomFeatureType.FROM_IMAGE_EMBEDDING
|
|
98
122
|
|
|
123
|
+
@validator('source_column')
|
|
124
|
+
def validate_source_column(cls, value: str) -> str: # noqa: N805
|
|
125
|
+
if value is None:
|
|
126
|
+
raise ValueError('source_column must be specified')
|
|
127
|
+
return value
|
|
128
|
+
|
|
99
129
|
|
|
100
130
|
class Enrichment(CustomFeature):
|
|
101
131
|
type: CustomFeatureType = CustomFeatureType.ENRICHMENT
|
fiddler3/__init__.py
CHANGED
|
@@ -1,4 +1,122 @@
|
|
|
1
1
|
from fiddler3.connection import Connection, ConnectionMixin, init # noqa
|
|
2
|
-
from fiddler3.
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
from fiddler3.constants.alert_rule import ( # noqa
|
|
3
|
+
AlertCondition,
|
|
4
|
+
BinSize,
|
|
5
|
+
CompareTo,
|
|
6
|
+
Priority,
|
|
7
|
+
)
|
|
8
|
+
from fiddler3.constants.baseline import BaselineType, WindowSize # noqa
|
|
9
|
+
from fiddler3.constants.dataset import EnvType # noqa
|
|
10
|
+
from fiddler3.constants.job import JobStatus # noqa
|
|
11
|
+
from fiddler3.constants.model import ( # noqa
|
|
12
|
+
ArtifactStatus,
|
|
13
|
+
CustomFeatureType,
|
|
14
|
+
DataType,
|
|
15
|
+
ModelInputType,
|
|
16
|
+
ModelTask,
|
|
17
|
+
)
|
|
18
|
+
from fiddler3.constants.model_deployment import ArtifactType, DeploymentType # noqa
|
|
19
|
+
from fiddler3.constants.xai import ExplainMethod # noqa
|
|
20
|
+
from fiddler3.entities.alert_record import AlertRecord # noqa
|
|
21
|
+
from fiddler3.entities.alert_rule import AlertRule # noqa
|
|
22
|
+
from fiddler3.entities.baseline import Baseline # noqa
|
|
23
|
+
from fiddler3.entities.dataset import Dataset # noqa
|
|
24
|
+
from fiddler3.entities.file import File # noqa
|
|
25
|
+
from fiddler3.entities.job import Job # noqa
|
|
26
|
+
from fiddler3.entities.model import Model # noqa
|
|
27
|
+
from fiddler3.entities.model_deployment import ModelDeployment # noqa
|
|
28
|
+
from fiddler3.entities.project import Project # noqa
|
|
29
|
+
from fiddler3.entities.webhook import Webhook # noqa
|
|
30
|
+
from fiddler3.exceptions import ( # noqa
|
|
31
|
+
ApiError,
|
|
32
|
+
AsyncJobFailed,
|
|
33
|
+
Conflict,
|
|
34
|
+
ConnectionError,
|
|
35
|
+
ConnectionTimeout,
|
|
36
|
+
HttpError,
|
|
37
|
+
IncompatibleClient,
|
|
38
|
+
NotFound,
|
|
39
|
+
Unsupported,
|
|
40
|
+
)
|
|
41
|
+
from fiddler3.schemas.custom_features import ( # noqa
|
|
42
|
+
CustomFeature,
|
|
43
|
+
Enrichment,
|
|
44
|
+
ImageEmbedding,
|
|
45
|
+
Multivariate,
|
|
46
|
+
TextEmbedding,
|
|
47
|
+
VectorFeature,
|
|
48
|
+
)
|
|
49
|
+
from fiddler3.schemas.dataset import EnvType # noqa
|
|
50
|
+
from fiddler3.schemas.deployment_params import DeploymentParams # noqa
|
|
51
|
+
from fiddler3.schemas.model_schema import ModelSchema # noqa
|
|
52
|
+
from fiddler3.schemas.model_spec import ModelSpec # noqa
|
|
53
|
+
from fiddler3.schemas.model_task_params import ModelTaskParams # noqa
|
|
54
|
+
from fiddler3.schemas.xai import ( # noqa
|
|
55
|
+
DatasetDataSource,
|
|
56
|
+
EventIdDataSource,
|
|
57
|
+
RowDataSource,
|
|
58
|
+
SqlSliceQueryDataSource,
|
|
59
|
+
)
|
|
60
|
+
from fiddler3.schemas.xai_params import XaiParams # noqa
|
|
61
|
+
from fiddler3.utils.logger import set_logging # noqa
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
'Connection',
|
|
65
|
+
'ConnectionMixin',
|
|
66
|
+
'init',
|
|
67
|
+
# Constants
|
|
68
|
+
'AlertCondition',
|
|
69
|
+
'ArtifactStatus',
|
|
70
|
+
'ArtifactType',
|
|
71
|
+
'BaselineType',
|
|
72
|
+
'BinSize',
|
|
73
|
+
'CompareTo',
|
|
74
|
+
'DataType',
|
|
75
|
+
'DeploymentType',
|
|
76
|
+
'EnvType',
|
|
77
|
+
'ExplainMethod',
|
|
78
|
+
'JobStatus',
|
|
79
|
+
'ModelInputType',
|
|
80
|
+
'ModelTask',
|
|
81
|
+
'Priority',
|
|
82
|
+
'WindowSize',
|
|
83
|
+
# Schemas
|
|
84
|
+
'CustomFeature',
|
|
85
|
+
'DatasetDataSource',
|
|
86
|
+
'DeploymentParams',
|
|
87
|
+
'Enrichment',
|
|
88
|
+
'EventIdDataSource',
|
|
89
|
+
'ImageEmbedding',
|
|
90
|
+
'ModelSchema',
|
|
91
|
+
'ModelSpec',
|
|
92
|
+
'ModelTaskParams',
|
|
93
|
+
'Multivariate',
|
|
94
|
+
'RowDataSource',
|
|
95
|
+
'SqlSliceQueryDataSource',
|
|
96
|
+
'TextEmbedding',
|
|
97
|
+
'VectorFeature',
|
|
98
|
+
'XaiParams',
|
|
99
|
+
# Entities
|
|
100
|
+
'AlertRecord',
|
|
101
|
+
'AlertRule',
|
|
102
|
+
'Baseline',
|
|
103
|
+
'Dataset',
|
|
104
|
+
'File',
|
|
105
|
+
'Job',
|
|
106
|
+
'Model',
|
|
107
|
+
'ModelDeployment',
|
|
108
|
+
'Project',
|
|
109
|
+
'Webhook',
|
|
110
|
+
# Exceptions
|
|
111
|
+
'NotFound',
|
|
112
|
+
'Conflict',
|
|
113
|
+
'IncompatibleClient',
|
|
114
|
+
'AsyncJobFailed',
|
|
115
|
+
'Unsupported',
|
|
116
|
+
'HttpError',
|
|
117
|
+
'ConnectionTimeout',
|
|
118
|
+
'ConnectionError',
|
|
119
|
+
'ApiError',
|
|
120
|
+
# Utilities
|
|
121
|
+
'set_logging',
|
|
122
|
+
]
|
fiddler3/configs.py
CHANGED
fiddler3/constants/model.py
CHANGED
|
@@ -62,3 +62,12 @@ class ArtifactStatus(str, enum.Enum):
|
|
|
62
62
|
NO_MODEL = 'no_model'
|
|
63
63
|
SURROGATE = 'surrogate'
|
|
64
64
|
USER_UPLOADED = 'user_uploaded'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@enum.unique
|
|
68
|
+
class CustomFeatureType(str, enum.Enum):
|
|
69
|
+
FROM_COLUMNS = 'FROM_COLUMNS'
|
|
70
|
+
FROM_VECTOR = 'FROM_VECTOR'
|
|
71
|
+
FROM_TEXT_EMBEDDING = 'FROM_TEXT_EMBEDDING'
|
|
72
|
+
FROM_IMAGE_EMBEDDING = 'FROM_IMAGE_EMBEDDING'
|
|
73
|
+
ENRICHMENT = 'ENRICHMENT'
|
fiddler3/entities/__init__.py
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"""Initialize all new entities."""
|
|
2
|
-
from fiddler3.entities.alert_record import AlertRecord # noqa
|
|
3
|
-
from fiddler3.entities.alert_rule import AlertRule # noqa
|
|
4
|
-
from fiddler3.entities.baseline import Baseline # noqa
|
|
5
|
-
from fiddler3.entities.dataset import Dataset # noqa
|
|
6
|
-
from fiddler3.entities.files import File # noqa
|
|
7
|
-
from fiddler3.entities.job import Job # noqa
|
|
8
|
-
from fiddler3.entities.model import Model # noqa
|
|
9
|
-
from fiddler3.entities.model_deployment import ModelDeployment # noqa
|
|
10
|
-
from fiddler3.entities.project import Project # noqa
|
fiddler3/entities/alert_rule.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import builtins
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from typing import Any, Iterator
|
|
5
6
|
from uuid import UUID
|
|
@@ -10,7 +11,7 @@ from fiddler3.entities.base import BaseEntity
|
|
|
10
11
|
from fiddler3.entities.baseline import Baseline, BaselineCompactMixin
|
|
11
12
|
from fiddler3.entities.model import Model, ModelCompactMixin
|
|
12
13
|
from fiddler3.entities.project import ProjectCompactMixin
|
|
13
|
-
from fiddler3.schemas.alert_rule import AlertRuleResp
|
|
14
|
+
from fiddler3.schemas.alert_rule import AlertRuleResp, NotificationConfig
|
|
14
15
|
from fiddler3.schemas.filter_query import OperatorType, QueryCondition, QueryRule
|
|
15
16
|
from fiddler3.utils.logger import get_logger
|
|
16
17
|
|
|
@@ -237,10 +238,86 @@ class AlertRule(
|
|
|
237
238
|
|
|
238
239
|
@handle_api_error
|
|
239
240
|
def disable_notifications(self) -> None:
|
|
240
|
-
"""
|
|
241
|
+
"""Disable notifications for an alert rule"""
|
|
241
242
|
self._client().patch(
|
|
242
243
|
url=self._get_url(id_=self.id), data={'enable_notification': False}
|
|
243
244
|
)
|
|
244
245
|
logger.info(
|
|
245
246
|
'Notifications have been disabled for alert rule with id: %s', self.id
|
|
246
247
|
)
|
|
248
|
+
|
|
249
|
+
@handle_api_error
|
|
250
|
+
def set_notification_config(
|
|
251
|
+
self,
|
|
252
|
+
emails: builtins.list[str] | None = None,
|
|
253
|
+
pagerduty_services: builtins.list[str] | None = None,
|
|
254
|
+
pagerduty_severity: str | None = None,
|
|
255
|
+
webhooks: builtins.list[UUID] | None = None,
|
|
256
|
+
) -> NotificationConfig:
|
|
257
|
+
"""
|
|
258
|
+
Set notifications for an alert rule
|
|
259
|
+
|
|
260
|
+
:param emails: list of emails
|
|
261
|
+
:param pagerduty_services: list of pagerduty services
|
|
262
|
+
:param pagerduty_severity: severity of pagerduty
|
|
263
|
+
:param webhooks: list of webhooks UUIDs
|
|
264
|
+
|
|
265
|
+
:return: AlertNotifications object
|
|
266
|
+
"""
|
|
267
|
+
notifications = {
|
|
268
|
+
'emails': {
|
|
269
|
+
'email': ','.join(emails) if emails else '',
|
|
270
|
+
},
|
|
271
|
+
'pagerduty': {
|
|
272
|
+
'service': ','.join(pagerduty_services) if pagerduty_services else '',
|
|
273
|
+
'severity': pagerduty_severity,
|
|
274
|
+
},
|
|
275
|
+
'webhooks': [{'uuid': webhook_uuid} for webhook_uuid in webhooks]
|
|
276
|
+
if webhooks
|
|
277
|
+
else None,
|
|
278
|
+
}
|
|
279
|
+
response = self._client().patch(
|
|
280
|
+
url=self._get_url(id_=self.id),
|
|
281
|
+
data={'notifications': notifications},
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return self._get_notifications_from_dict(
|
|
285
|
+
response.json()['data']['notifications']
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
@handle_api_error
|
|
289
|
+
def get_notification_config(self) -> NotificationConfig:
|
|
290
|
+
"""
|
|
291
|
+
Get notifications setting for an alert rule
|
|
292
|
+
|
|
293
|
+
:return: AlertNotifications object
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
response = self._client().get(url=self._get_url(id_=self.id))
|
|
297
|
+
|
|
298
|
+
return self._get_notifications_from_dict(
|
|
299
|
+
response.json()['data']['notifications']
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def _get_notifications_from_dict(notif_dict: dict) -> NotificationConfig:
|
|
304
|
+
emails = notif_dict.get('emails', {}).get('email')
|
|
305
|
+
notifications: dict = {}
|
|
306
|
+
if emails:
|
|
307
|
+
notifications['emails'] = [email.strip() for email in emails.split(',')]
|
|
308
|
+
|
|
309
|
+
pd_services = notif_dict.get('pagerduty', {}).get('service')
|
|
310
|
+
if pd_services:
|
|
311
|
+
notifications['pagerduty_services'] = [
|
|
312
|
+
pd_service.strip() for pd_service in pd_services.split(',')
|
|
313
|
+
]
|
|
314
|
+
notifications['pagerduty_severity'] = notif_dict.get('pagerduty', {}).get(
|
|
315
|
+
'severity'
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if notif_dict.get('webhooks'):
|
|
319
|
+
notifications['webhooks'] = [
|
|
320
|
+
webhook['uuid'] for webhook in notif_dict.get('webhooks', [])
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
return NotificationConfig(**notifications)
|
fiddler3/entities/baseline.py
CHANGED
|
@@ -9,11 +9,11 @@ from fiddler3.constants.dataset import EnvType
|
|
|
9
9
|
from fiddler3.decorators import handle_api_error
|
|
10
10
|
from fiddler3.entities.base import BaseEntity
|
|
11
11
|
from fiddler3.entities.dataset import DatasetCompactMixin
|
|
12
|
-
from fiddler3.entities.helpers import raise_not_found
|
|
13
12
|
from fiddler3.entities.model import ModelCompactMixin
|
|
14
13
|
from fiddler3.entities.project import ProjectCompactMixin
|
|
15
14
|
from fiddler3.schemas.baseline import BaselineResp
|
|
16
15
|
from fiddler3.schemas.filter_query import OperatorType, QueryCondition, QueryRule
|
|
16
|
+
from fiddler3.utils.helpers import raise_not_found
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Baseline(
|
|
@@ -138,9 +138,9 @@ class Baseline(
|
|
|
138
138
|
"""
|
|
139
139
|
Get the baseline instance using baseline name, model name and project name.
|
|
140
140
|
|
|
141
|
-
:param name:
|
|
142
|
-
:param model_name:
|
|
143
|
-
:param project_name:
|
|
141
|
+
:param name: Baseline name
|
|
142
|
+
:param model_name: Name of the model associated with the baseline
|
|
143
|
+
:param project_name: Name of the project associated with the baseline
|
|
144
144
|
:return: Baseline instance for the provided params
|
|
145
145
|
"""
|
|
146
146
|
|
|
@@ -179,19 +179,25 @@ class Baseline(
|
|
|
179
179
|
@handle_api_error
|
|
180
180
|
def create(self) -> Baseline:
|
|
181
181
|
"""Create a new baseline."""
|
|
182
|
+
payload = {
|
|
183
|
+
'name': self.name,
|
|
184
|
+
'model_id': self.model_id,
|
|
185
|
+
'type': self.type,
|
|
186
|
+
'env_type': self.environment,
|
|
187
|
+
'env_id': self.dataset_id,
|
|
188
|
+
}
|
|
189
|
+
if self.start_time:
|
|
190
|
+
payload['start_time'] = self.start_time
|
|
191
|
+
if self.end_time:
|
|
192
|
+
payload['end_time'] = self.end_time
|
|
193
|
+
if self.offset:
|
|
194
|
+
payload['offset'] = self.offset
|
|
195
|
+
if self.window_size:
|
|
196
|
+
payload['window_size'] = self.window_size
|
|
197
|
+
|
|
182
198
|
response = self._client().post(
|
|
183
199
|
url=self._get_url(),
|
|
184
|
-
data=
|
|
185
|
-
'name': self.name,
|
|
186
|
-
'model_id': self.model_id,
|
|
187
|
-
'type': self.type,
|
|
188
|
-
'env_type': self.environment,
|
|
189
|
-
'start_time': self.start_time,
|
|
190
|
-
'end_time': self.end_time,
|
|
191
|
-
'offset': self.offset,
|
|
192
|
-
'window_size': self.window_size,
|
|
193
|
-
'env_id': self.dataset_id,
|
|
194
|
-
},
|
|
200
|
+
data=payload,
|
|
195
201
|
)
|
|
196
202
|
self._refresh_from_response(response=response)
|
|
197
203
|
return self
|
fiddler3/entities/dataset.py
CHANGED
|
@@ -7,10 +7,10 @@ from uuid import UUID
|
|
|
7
7
|
from fiddler3.constants.dataset import EnvType
|
|
8
8
|
from fiddler3.decorators import handle_api_error
|
|
9
9
|
from fiddler3.entities.base import BaseEntity
|
|
10
|
-
from fiddler3.entities.helpers import raise_not_found
|
|
11
10
|
from fiddler3.entities.project import ProjectCompactMixin
|
|
12
11
|
from fiddler3.schemas.dataset import DatasetResp
|
|
13
12
|
from fiddler3.schemas.filter_query import OperatorType, QueryCondition, QueryRule
|
|
13
|
+
from fiddler3.utils.helpers import raise_not_found
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Dataset(BaseEntity, ProjectCompactMixin):
|
fiddler3/entities/events.py
CHANGED
|
@@ -1,70 +1,94 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Iterator
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
from requests import Response
|
|
9
9
|
|
|
10
|
+
from fiddler3.configs import STREAM_EVENT_LIMIT
|
|
11
|
+
from fiddler3.connection import ConnectionMixin
|
|
10
12
|
from fiddler3.decorators import handle_api_error
|
|
11
|
-
from fiddler3.entities.
|
|
13
|
+
from fiddler3.entities.file import File
|
|
12
14
|
from fiddler3.entities.job import Job
|
|
13
15
|
from fiddler3.schemas.dataset import EnvType
|
|
14
16
|
from fiddler3.schemas.events import EventsSource, FileSource
|
|
15
17
|
from fiddler3.schemas.job import JobCompactResp
|
|
16
18
|
|
|
17
|
-
STREAM_EVENTS_MAX = 1000
|
|
18
19
|
|
|
20
|
+
class EventPublisher(ConnectionMixin):
|
|
21
|
+
STREAM_LIMIT = STREAM_EVENT_LIMIT
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
def __init__(self, model_id: UUID) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Event publishing methods
|
|
26
|
+
|
|
27
|
+
:param model_id: Model identifier
|
|
28
|
+
"""
|
|
29
|
+
self.model_id = model_id
|
|
30
|
+
|
|
31
|
+
def _get_stream_chunks(
|
|
32
|
+
self, source: pd.DataFrame | list[dict[str, Any]]
|
|
33
|
+
) -> Iterator[list[dict[str, Any]]]:
|
|
34
|
+
"""Chunk the source based on stream limit"""
|
|
35
|
+
|
|
36
|
+
for i in range(0, len(source), self.STREAM_LIMIT):
|
|
37
|
+
chunk = source[i : i + self.STREAM_LIMIT]
|
|
38
|
+
|
|
39
|
+
if isinstance(chunk, pd.DataFrame):
|
|
40
|
+
events = chunk.to_dict('records')
|
|
41
|
+
else:
|
|
42
|
+
events = chunk
|
|
43
|
+
|
|
44
|
+
yield events
|
|
23
45
|
|
|
24
46
|
@handle_api_error
|
|
25
47
|
def publish(
|
|
26
48
|
self,
|
|
27
|
-
source: list[dict[
|
|
28
|
-
environment: EnvType,
|
|
49
|
+
source: list[dict[str, Any]] | str | Path | pd.DataFrame,
|
|
50
|
+
environment: EnvType = EnvType.PRODUCTION,
|
|
29
51
|
dataset_name: str | None = None,
|
|
52
|
+
update: bool = False,
|
|
30
53
|
) -> list[UUID] | Job:
|
|
31
54
|
"""
|
|
32
55
|
Publish Pre-production or Production data
|
|
33
56
|
|
|
34
|
-
:param source:
|
|
57
|
+
:param source: one of:
|
|
35
58
|
Path or str path: path for data file.
|
|
36
|
-
list[dict]: list of event dicts
|
|
59
|
+
list[dict]: list of event dicts (max 1000) EnvType.PRE_PRODUCTION is not supported
|
|
37
60
|
dataframe: events dataframe. EnvType.PRE_PRODUCTION not supported.
|
|
38
|
-
:param environment:
|
|
39
|
-
:param dataset_name:
|
|
61
|
+
:param environment: Either EnvType.PRE_PRODUCTION or EnvType.PRODUCTION
|
|
62
|
+
:param dataset_name: Name of the dataset. Not supported for EnvType.PRODUCTION
|
|
63
|
+
:param update: flag indicating if the events are updates to previously published rows
|
|
40
64
|
|
|
41
65
|
:return: list[UUID] for list of dicts or dataframe source and Job object for file path source.
|
|
42
66
|
"""
|
|
43
|
-
|
|
44
|
-
if isinstance(_sources, FileSource):
|
|
67
|
+
if isinstance(source, (str, Path)):
|
|
45
68
|
return self._publish_file(
|
|
46
|
-
source=
|
|
69
|
+
source=source,
|
|
47
70
|
environment=environment,
|
|
48
71
|
dataset_name=dataset_name,
|
|
72
|
+
update=update,
|
|
49
73
|
)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
)
|
|
74
|
+
|
|
75
|
+
if isinstance(source, (list, pd.DataFrame)):
|
|
76
|
+
return self._publish_stream(source=source, update=update)
|
|
77
|
+
|
|
78
|
+
raise ValueError(f'Unsupported source - {type(source)}')
|
|
55
79
|
|
|
56
80
|
def _publish_stream(
|
|
57
81
|
self,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
dataset_name: str | None = None,
|
|
82
|
+
source: list[dict[str, Any]] | pd.DataFrame,
|
|
83
|
+
update: bool = False,
|
|
61
84
|
) -> list[UUID]:
|
|
62
85
|
fiddler_ids = []
|
|
63
|
-
for
|
|
86
|
+
for events in self._get_stream_chunks(source=source):
|
|
64
87
|
response = self._publish_call(
|
|
65
|
-
source=
|
|
66
|
-
environment=
|
|
67
|
-
dataset_name=
|
|
88
|
+
source=EventsSource(events=events),
|
|
89
|
+
environment=EnvType.PRODUCTION,
|
|
90
|
+
dataset_name=None,
|
|
91
|
+
update=update,
|
|
68
92
|
)
|
|
69
93
|
fiddler_ids.extend(response.json()['data']['fiddler_ids'])
|
|
70
94
|
|
|
@@ -72,48 +96,40 @@ class EventMixin:
|
|
|
72
96
|
|
|
73
97
|
def _publish_file(
|
|
74
98
|
self,
|
|
75
|
-
source:
|
|
99
|
+
source: Path | str,
|
|
76
100
|
environment: EnvType,
|
|
77
101
|
dataset_name: str | None = None,
|
|
102
|
+
update: bool = False,
|
|
78
103
|
) -> Job:
|
|
104
|
+
file = File(path=Path(source)).upload()
|
|
105
|
+
|
|
106
|
+
assert file.id is not None
|
|
107
|
+
|
|
79
108
|
response = self._publish_call(
|
|
80
|
-
source=
|
|
109
|
+
source=FileSource(file_id=file.id),
|
|
110
|
+
environment=environment,
|
|
111
|
+
dataset_name=dataset_name,
|
|
112
|
+
update=update,
|
|
81
113
|
)
|
|
82
114
|
job_compact = JobCompactResp(**response.json()['data']['job'])
|
|
83
115
|
return Job.get(id_=job_compact.id)
|
|
84
116
|
|
|
85
|
-
def _get_sources(
|
|
86
|
-
self, source: list[dict[str, Any]] | str | Path | pd.DataFrame
|
|
87
|
-
) -> list[EventsSource] | FileSource:
|
|
88
|
-
_source = []
|
|
89
|
-
if isinstance(source, (str, Path)):
|
|
90
|
-
file = File(path=Path(source)).upload()
|
|
91
|
-
assert file.id is not None
|
|
92
|
-
return FileSource(file_id=file.id)
|
|
93
|
-
|
|
94
|
-
if isinstance(source, list):
|
|
95
|
-
_source.append(EventsSource(events=source))
|
|
96
|
-
|
|
97
|
-
elif isinstance(source, pd.DataFrame):
|
|
98
|
-
for event_dict in [
|
|
99
|
-
source[i : i + STREAM_EVENTS_MAX]
|
|
100
|
-
for i in range(0, source.shape[0], STREAM_EVENTS_MAX)
|
|
101
|
-
]:
|
|
102
|
-
_source.append(EventsSource(events=event_dict.to_dict('records')))
|
|
103
|
-
|
|
104
|
-
return _source
|
|
105
|
-
|
|
106
117
|
def _publish_call(
|
|
107
118
|
self,
|
|
108
119
|
source: FileSource | EventsSource,
|
|
109
120
|
environment: EnvType,
|
|
110
121
|
dataset_name: str | None = None,
|
|
122
|
+
update: bool = False,
|
|
111
123
|
) -> Response:
|
|
112
|
-
|
|
124
|
+
if update:
|
|
125
|
+
method = self._client().patch
|
|
126
|
+
else:
|
|
127
|
+
method = self._client().post
|
|
128
|
+
return method(
|
|
113
129
|
url='/v3/events',
|
|
114
130
|
data={
|
|
115
131
|
'source': source.dict(),
|
|
116
|
-
'model_id': self.
|
|
132
|
+
'model_id': self.model_id,
|
|
117
133
|
'env_type': environment,
|
|
118
134
|
'env_name': dataset_name,
|
|
119
135
|
},
|