fiddler-client 2.3.0.dev1__py3-none-any.whl → 2.4.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. fiddler/__init__.py +2 -0
  2. fiddler/_version.py +1 -1
  3. fiddler/api/alert_mixin.py +3 -1
  4. fiddler/api/events_mixin.py +5 -5
  5. fiddler/api/webhooks_mixin.py +8 -5
  6. fiddler/core_objects.py +98 -47
  7. fiddler/schemas/custom_features.py +25 -19
  8. fiddler3/constants/baseline.py +19 -0
  9. fiddler3/constants/common.py +12 -0
  10. fiddler3/constants/dataset.py +7 -0
  11. fiddler3/constants/model_deployment.py +15 -0
  12. fiddler3/constants/xai.py +11 -0
  13. fiddler3/entities/__init__.py +5 -1
  14. fiddler3/entities/base.py +22 -18
  15. fiddler3/entities/baseline.py +228 -0
  16. fiddler3/entities/dataset.py +149 -0
  17. fiddler3/entities/helpers.py +1 -1
  18. fiddler3/entities/job.py +9 -8
  19. fiddler3/entities/model.py +160 -29
  20. fiddler3/entities/model_artifact.py +350 -0
  21. fiddler3/entities/model_deployment.py +112 -0
  22. fiddler3/entities/model_surrogate.py +92 -0
  23. fiddler3/entities/organization.py +38 -0
  24. fiddler3/entities/project.py +69 -8
  25. fiddler3/entities/user.py +45 -0
  26. fiddler3/entities/xai.py +309 -0
  27. fiddler3/libs/http_client.py +24 -13
  28. fiddler3/libs/json_encoder.py +12 -0
  29. fiddler3/schemas/__init__.py +4 -0
  30. fiddler3/schemas/baseline.py +37 -0
  31. fiddler3/schemas/custom_features.py +23 -18
  32. fiddler3/schemas/dataset.py +27 -0
  33. fiddler3/schemas/deployment_params.py +25 -0
  34. fiddler3/schemas/filter_query.py +54 -0
  35. fiddler3/schemas/job.py +2 -2
  36. fiddler3/schemas/model.py +9 -9
  37. fiddler3/schemas/model_artifact.py +6 -0
  38. fiddler3/schemas/model_deployment.py +26 -0
  39. fiddler3/schemas/organization.py +9 -1
  40. fiddler3/schemas/project.py +4 -4
  41. fiddler3/schemas/server_info.py +2 -2
  42. fiddler3/schemas/user.py +1 -1
  43. fiddler3/schemas/xai.py +32 -0
  44. fiddler3/tests/apis/test_baseline.py +292 -0
  45. fiddler3/tests/apis/test_dataset.py +173 -0
  46. fiddler3/tests/apis/test_mixin.py +77 -0
  47. fiddler3/tests/apis/test_model.py +102 -7
  48. fiddler3/tests/apis/test_model_artifact.py +175 -0
  49. fiddler3/tests/apis/test_model_deployment.py +98 -0
  50. fiddler3/tests/apis/test_model_surrogate.py +159 -0
  51. fiddler3/tests/apis/test_project.py +36 -20
  52. fiddler3/tests/apis/test_xai.py +690 -0
  53. fiddler3/tests/constants.py +10 -0
  54. fiddler3/tests/test_json_encoder.py +16 -0
  55. fiddler3/tests/test_logger.py +12 -0
  56. fiddler3/tests/test_utils.py +19 -0
  57. fiddler3/utils/__init__.py +1 -0
  58. fiddler3/utils/helpers.py +43 -0
  59. fiddler3/utils/logger.py +15 -0
  60. fiddler3/utils/validations.py +11 -0
  61. {fiddler_client-2.3.0.dev1.dist-info → fiddler_client-2.4.0.dev1.dist-info}/METADATA +25 -3
  62. {fiddler_client-2.3.0.dev1.dist-info → fiddler_client-2.4.0.dev1.dist-info}/RECORD +65 -34
  63. {fiddler_client-2.3.0.dev1.dist-info → fiddler_client-2.4.0.dev1.dist-info}/LICENSE.txt +0 -0
  64. {fiddler_client-2.3.0.dev1.dist-info → fiddler_client-2.4.0.dev1.dist-info}/WHEEL +0 -0
  65. {fiddler_client-2.3.0.dev1.dist-info → fiddler_client-2.4.0.dev1.dist-info}/top_level.txt +0 -0
fiddler/__init__.py CHANGED
@@ -50,6 +50,7 @@ from fiddler.schemas.custom_features import (
50
50
  Multivariate,
51
51
  TextEmbedding,
52
52
  VectorFeature,
53
+ Enrichment,
53
54
  )
54
55
  from fiddler.utils import ColorLogger
55
56
  from fiddler.utils.logger import get_logger
@@ -71,6 +72,7 @@ __all__ = [
71
72
  'VectorFeature',
72
73
  'TextEmbedding',
73
74
  'ImageEmbedding',
75
+ 'Enrichment',
74
76
  'ColorLogger',
75
77
  'DatasetInfo',
76
78
  'DataType',
fiddler/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '2.3.0.dev1'
1
+ __version__ = '2.4.0.dev1'
@@ -60,7 +60,7 @@ class AlertMixin:
60
60
  warning_threshold: Optional[float] = None,
61
61
  notifications_config: Optional[Dict[str, Any]] = None,
62
62
  ) -> AlertRule:
63
- f"""
63
+ """
64
64
  To add an alert rule
65
65
 
66
66
  :param project_id: Unique project name for which the alert rule is created
@@ -158,6 +158,8 @@ class AlertMixin:
158
158
  time_bucket=bin_size.value[0],
159
159
  bin_size=bin_size.value[1],
160
160
  notifications=notifications_config,
161
+ feature_name=None,
162
+ feature_names=None,
161
163
  )
162
164
 
163
165
  if columns and not match_semver(
@@ -34,7 +34,7 @@ class EventsMixin:
34
34
  model_name: str,
35
35
  events_path: Path,
36
36
  batch_id: Optional[str] = None,
37
- events_schema: Optional[str] = None,
37
+ events_schema: Optional[dict] = None,
38
38
  id_field: Optional[str] = None,
39
39
  is_update: Optional[bool] = None,
40
40
  timestamp_field: Optional[str] = None,
@@ -181,12 +181,12 @@ class EventsMixin:
181
181
 
182
182
  # Either batch_source or events_path can be given for _publish_events_batch_from_file
183
183
  if batch_source is None and events_path is None:
184
- raise ValueError(f'`batch_source` parameter is required.')
184
+ raise ValueError('`batch_source` parameter is required.')
185
185
 
186
186
  if timestamp_format:
187
187
  if type(timestamp_format) is not FiddlerTimestamp:
188
188
  raise ValueError(
189
- f'timestamp_format format is not of type FiddlerTimestamp'
189
+ 'timestamp_format format is not of type FiddlerTimestamp'
190
190
  )
191
191
  timestamp_format = FiddlerTimestamp(timestamp_format.value)
192
192
  if type(batch_source) == pd.DataFrame and (
@@ -235,7 +235,7 @@ class EventsMixin:
235
235
  event: dict,
236
236
  event_id: Optional[str] = None,
237
237
  update_event: Optional[bool] = None,
238
- event_timestamp: Optional[int] = None,
238
+ event_timestamp: Optional[str] = None,
239
239
  timestamp_format: FiddlerTimestamp = FiddlerTimestamp.INFER,
240
240
  casting_type: Optional[bool] = False,
241
241
  dry_run: Optional[bool] = False,
@@ -263,7 +263,7 @@ class EventsMixin:
263
263
  if timestamp_format:
264
264
  if type(timestamp_format) is not FiddlerTimestamp:
265
265
  raise ValueError(
266
- f'timestamp_format format is not of type FiddlerTimestamp'
266
+ 'timestamp_format format is not of type FiddlerTimestamp'
267
267
  )
268
268
  timestamp_format = FiddlerTimestamp(timestamp_format.value)
269
269
 
@@ -7,10 +7,7 @@ from fiddler.libs.http_client import RequestClient
7
7
  from fiddler.schema.webhook import Webhook
8
8
  from fiddler.utils.decorators import handle_api_error_response
9
9
  from fiddler.utils.logger import get_logger
10
- from fiddler.utils.response_handler import (
11
- APIResponseHandler,
12
- PaginatedResponseHandler,
13
- )
10
+ from fiddler.utils.response_handler import APIResponseHandler, PaginatedResponseHandler
14
11
 
15
12
  logger = get_logger(__name__)
16
13
 
@@ -83,7 +80,13 @@ class WebhookMixin:
83
80
 
84
81
  :returns: Updated `Webhook` object.
85
82
  """
86
- request_body = Webhook(name=name, url=url, provider=provider).dict()
83
+ request_body = Webhook(
84
+ uuid=uuid,
85
+ organization_name=self.organization_name,
86
+ name=name,
87
+ url=url,
88
+ provider=provider,
89
+ ).dict()
87
90
  response = self.client.patch(
88
91
  url=f'webhooks/{uuid}',
89
92
  data=request_body,
fiddler/core_objects.py CHANGED
@@ -28,7 +28,9 @@ from pandas.api.types import CategoricalDtype
28
28
 
29
29
  from fiddler.schemas.custom_features import (
30
30
  CustomFeature,
31
+ Enrichment,
31
32
  ImageEmbedding,
33
+ Multivariate,
32
34
  TextEmbedding,
33
35
  VectorFeature,
34
36
  )
@@ -1235,18 +1237,27 @@ class ModelInfo:
1235
1237
  + self.get_target_names()
1236
1238
  + self.get_metadata_names()
1237
1239
  )
1240
+
1238
1241
  numeric_cols = self.get_input_pandas_dtypes()
1239
1242
  if self.metadata:
1240
1243
  numeric_cols.update(self.get_metadata_pandas_dtypes())
1241
1244
  if self.targets:
1242
1245
  numeric_cols.update(self.get_target_pandas_dtypes())
1243
- valid_col_names = [
1246
+
1247
+ valid_numeric_names = [
1244
1248
  k
1245
1249
  for k, v in numeric_cols.items()
1246
1250
  if v in (DataType.INTEGER.value, DataType.FLOAT.value)
1247
1251
  ]
1252
+
1253
+ valid_enrichment_names = [
1254
+ cf.name for cf in self.custom_features if isinstance(cf, Enrichment)
1255
+ ]
1256
+
1248
1257
  valid_vector_cols = [
1249
- input.name for input in self.inputs if input.data_type.is_vector() # type: ignore
1258
+ vec_col.name
1259
+ for vec_col in (self.inputs or []) + (self.metadata or [])
1260
+ if vec_col.data_type.is_vector()
1250
1261
  ]
1251
1262
 
1252
1263
  unique_cf_names = []
@@ -1268,17 +1279,77 @@ class ModelInfo:
1268
1279
  else:
1269
1280
  unique_cf_names.append(feature.name)
1270
1281
 
1271
- if isinstance(feature, (TextEmbedding, ImageEmbedding, VectorFeature)):
1272
- if not feature.column in valid_vector_cols:
1273
- raise ValueError(
1274
- f"Custom features '{feature.name}' defined on column '{feature.column}' is not found"
1275
- )
1276
- else:
1277
- for col in feature.columns: # type: ignore
1278
- if col not in valid_col_names:
1279
- raise ValueError(
1280
- f"Custom features '{feature.name}' defined on column '{col}' is not found or not of type int/float."
1281
- )
1282
+ if isinstance(feature, Multivariate):
1283
+ self.validate_multivariate_cf(feature, valid_numeric_names)
1284
+ elif isinstance(feature, TextEmbedding):
1285
+ self.validate_text_embedding_cf(
1286
+ feature,
1287
+ available_cols,
1288
+ valid_vector_cols,
1289
+ valid_enrichment_names,
1290
+ )
1291
+ elif isinstance(feature, VectorFeature):
1292
+ self.validate_vector_cf(feature, valid_vector_cols)
1293
+ elif isinstance(feature, Enrichment):
1294
+ self.validate_enrichment_cf(
1295
+ feature, available_cols, valid_enrichment_names
1296
+ )
1297
+
1298
+ def validate_enrichment_cf(
1299
+ self,
1300
+ feature: Enrichment,
1301
+ available_cols: List[str],
1302
+ valid_enrichment_names: List[str],
1303
+ ) -> None:
1304
+ if not feature.columns:
1305
+ raise ValueError(
1306
+ f'{feature.name} ({feature.__class__.__name__}) must specify at least one enrichment/column in columns.'
1307
+ )
1308
+ for column in feature.columns:
1309
+ if column == feature.name:
1310
+ raise ValueError(
1311
+ f'{feature.name} ({feature.__class__.__name__}) cannot depend on itself.'
1312
+ )
1313
+ if column not in [*valid_enrichment_names, *available_cols]:
1314
+ raise ValueError(
1315
+ f"Column '{column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist in the data or as an enrichment."
1316
+ )
1317
+
1318
+ def validate_text_embedding_cf(
1319
+ self,
1320
+ feature: TextEmbedding,
1321
+ available_cols: List[str],
1322
+ valid_vector_cols: List[str],
1323
+ valid_enrichment_names: List[str],
1324
+ ) -> None:
1325
+ if feature.column not in valid_vector_cols:
1326
+ # we require either a column or source_column
1327
+ if feature.source_column not in available_cols:
1328
+ raise ValueError(
1329
+ f'{feature.name} ({feature.__class__.__name__}) needs to specify at least one of [column, source_col].'
1330
+ )
1331
+ # column could be the result of an enrichment
1332
+ if feature.column not in valid_enrichment_names:
1333
+ raise ValueError(
1334
+ f"Column/Enrichment'{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
1335
+ )
1336
+
1337
+ def validate_vector_cf(self, feature: VectorFeature, valid_vector_cols: List[str]) -> None:
1338
+ if feature.column not in valid_vector_cols:
1339
+ raise ValueError(
1340
+ f"Column '{feature.column}' defined in {feature.name} ({feature.__class__.__name__}) does not exist or not of type vector."
1341
+ )
1342
+
1343
+ def validate_multivariate_cf(self, feature: Multivariate, valid_numeric_names: List[str]) -> None:
1344
+ if len(feature.columns or []) < 2:
1345
+ raise ValueError(
1346
+ f'{feature.name} ({feature.__class__.__name__}) must specify at least two columns.'
1347
+ )
1348
+ for col in feature.columns:
1349
+ if col not in valid_numeric_names:
1350
+ raise ValueError(
1351
+ f"Column '{col}' defined on {feature.name} ({feature.__class__.__name__}) does not exist or not of type int/float."
1352
+ )
1282
1353
 
1283
1354
  def warn_deprecated_parameter(self, param_name: str, from_version: str) -> None:
1284
1355
  warnings.warn(
@@ -2659,7 +2730,7 @@ class ModelInfo:
2659
2730
  validate_sanitized_names(self.decisions or [], sanitized_name_dict)
2660
2731
 
2661
2732
  def get_all_cols(self) -> List[Column]:
2662
- result: List = copy.deepcopy(self.inputs) # type: ignore
2733
+ result: List = copy.deepcopy(self.inputs) # type: ignore
2663
2734
  if self.outputs is not None:
2664
2735
  result += self.outputs
2665
2736
  if self.metadata is not None:
@@ -2756,41 +2827,21 @@ def _summary_dataframe_for_custom_features(
2756
2827
  ) -> pd.DataFrame:
2757
2828
  """
2758
2829
  Example:
2759
- name column type transformation n_clusters monitor monitor_components
2760
- 0 F1 'col_name1' FROM_TEXT 'Word2vec' 3 True False
2761
- 1 F2 ['col_name2','col_name3'] FROM_COLUMNS None 4 True False
2762
- 2 F3 'col_name4' FROM_VECTOR None auto True False
2763
- 3 F4 'col_name5' FROM_DICTIONARY None auto True False
2830
+ name column type n_clusters monitor_components
2831
+ 0 F1 'col_name1' FROM_TEXT 3 False
2832
+ 1 F2 ['col_name2','col_name3'] FROM_COLUMNS 4 False
2833
+ 2 F3 'col_name4' FROM_VECTOR auto False
2764
2834
  """ # noqa E501
2765
- names = []
2766
- columns = []
2767
- types = []
2768
- clusters: List[Any] = []
2769
- source_columns = []
2770
- embeddings = []
2771
-
2772
- for feature in custom_features:
2773
- names.append(feature.name)
2774
- types.append(feature.type.name)
2775
- columns.append(feature.column)
2776
- source_columns.append(feature.source_column)
2777
- embeddings.append(feature.column)
2778
-
2779
- if feature.n_clusters is not None:
2780
- clusters.append(feature.n_clusters)
2781
- else:
2782
- clusters.append('auto')
2835
+ data = []
2783
2836
 
2784
- return pd.DataFrame(
2785
- {
2786
- 'name': names,
2787
- 'type': types,
2788
- 'column': columns,
2789
- 'source': source_columns,
2790
- 'embeddings': embeddings,
2791
- 'n_clusters': clusters,
2792
- }
2793
- )
2837
+ for custom_feature in custom_features:
2838
+ # Convert each model instance to a dictionary
2839
+ model_dict = custom_feature.dict()
2840
+ data.append(model_dict)
2841
+
2842
+ # Create DataFrame from the combined data
2843
+ df = pd.DataFrame(data)
2844
+ return df
2794
2845
 
2795
2846
 
2796
2847
  @enum.unique
@@ -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
4
+ from pydantic import BaseModel, Extra
5
5
 
6
6
  CustomFeatureTypeVar = TypeVar('CustomFeatureTypeVar', bound='CustomFeature')
7
7
  DEFAULT_NUM_CLUSTERS = 5
@@ -13,6 +13,7 @@ class CustomFeatureType(str, Enum):
13
13
  FROM_VECTOR = 'FROM_VECTOR'
14
14
  FROM_TEXT_EMBEDDING = 'FROM_TEXT_EMBEDDING'
15
15
  FROM_IMAGE_EMBEDDING = 'FROM_IMAGE_EMBEDDING'
16
+ ENRICHMENT = 'ENRICHMENT'
16
17
 
17
18
 
18
19
  class CustomFeature(BaseModel):
@@ -20,13 +21,10 @@ class CustomFeature(BaseModel):
20
21
  type: CustomFeatureType
21
22
  n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
22
23
  centroids: Optional[List] = None
23
- columns: Optional[List[str]] = None
24
- column: Optional[str] = None
25
- source_column: Optional[str] = None
26
- n_tags: Optional[int] = DEFAULT_NUM_TAGS
27
24
 
28
25
  class Config:
29
26
  allow_mutation = False
27
+ extra = Extra.forbid
30
28
 
31
29
  @classmethod
32
30
  def from_columns(
@@ -49,30 +47,31 @@ class CustomFeature(BaseModel):
49
47
  return TextEmbedding.parse_obj(deserialized_json)
50
48
  elif feature_type == CustomFeatureType.FROM_IMAGE_EMBEDDING:
51
49
  return ImageEmbedding.parse_obj(deserialized_json)
50
+ elif feature_type == CustomFeatureType.ENRICHMENT:
51
+ return Enrichment.parse_obj(deserialized_json)
52
52
  else:
53
53
  raise ValueError(f'Unsupported feature type: {feature_type}')
54
54
 
55
55
  def to_dict(self) -> Dict[str, Any]:
56
- return_dict = {
56
+ return_dict: Dict[str, Any] = {
57
57
  'name': self.name,
58
58
  'type': self.type.value,
59
59
  'n_clusters': self.n_clusters,
60
60
  }
61
- if self.type == CustomFeatureType.FROM_COLUMNS:
62
- return_dict['columns'] = self.columns # type: ignore
63
- elif self.type == CustomFeatureType.FROM_VECTOR:
61
+ if isinstance(self, Multivariate):
62
+ return_dict['columns'] = self.columns
63
+ elif isinstance(self, VectorFeature):
64
64
  return_dict['column'] = self.column
65
- elif self.type in (
66
- CustomFeatureType.FROM_TEXT_EMBEDDING,
67
- CustomFeatureType.FROM_IMAGE_EMBEDDING,
68
- ):
69
- return_dict['source_column'] = self.source_column
70
- return_dict['column'] = self.column
71
-
72
- if self.type == CustomFeatureType.FROM_TEXT_EMBEDDING:
73
- return_dict['n_tags'] = self.n_tags
65
+ if isinstance(self, (ImageEmbedding, TextEmbedding)):
66
+ return_dict['source_column'] = self.source_column
67
+ if isinstance(self, TextEmbedding):
68
+ return_dict['n_tags'] = self.n_tags
69
+ elif isinstance(self, Enrichment):
70
+ return_dict['columns'] = self.columns
71
+ return_dict['enrichment'] = self.enrichment
72
+ return_dict['config'] = self.config
74
73
  else:
75
- raise ValueError(f'Unsupported feature type: {self.type}')
74
+ raise ValueError(f'Unsupported feature type: {self.type} {type(self)}')
76
75
 
77
76
  return return_dict
78
77
 
@@ -96,3 +95,10 @@ class TextEmbedding(VectorFeature):
96
95
 
97
96
  class ImageEmbedding(VectorFeature):
98
97
  type: CustomFeatureType = CustomFeatureType.FROM_IMAGE_EMBEDDING
98
+
99
+
100
+ class Enrichment(CustomFeature):
101
+ type: CustomFeatureType = CustomFeatureType.ENRICHMENT
102
+ columns: List[str]
103
+ enrichment: str
104
+ config: Dict[str, Any] = {}
@@ -0,0 +1,19 @@
1
+ import enum
2
+
3
+
4
+ @enum.unique
5
+ class BaselineType(str, enum.Enum):
6
+ STATIC = 'STATIC'
7
+ ROLLING = 'ROLLING'
8
+
9
+
10
+ @enum.unique
11
+ class WindowSize(enum.IntEnum):
12
+ FIVE_MINUTES = 300
13
+ ONE_HOUR = 3600
14
+ ONE_DAY = 86400
15
+ ONE_WEEK = 604800
16
+ ONE_MONTH = 2592000
17
+
18
+ def __str__(self) -> str:
19
+ return self.name
@@ -1,3 +1,15 @@
1
1
  JSON_CONTENT_TYPE = 'application/json'
2
2
  FIDDLER_CLIENT_VERSION_HEADER = 'X-Fiddler-Client-Version'
3
3
  CLIENT_NAME = 'python-sdk'
4
+ LOGGER_NAME = 'fiddler3'
5
+ LOG_FORMAT = '%(asctime)s [%(name)s:%(lineno)d] %(levelname)s: %(message)s'
6
+ TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S.%f'
7
+
8
+ # Headers
9
+ CONTENT_TYPE_HEADER_KEY = 'Content-Type'
10
+ CONTENT_TYPE_OCTET_STREAM = 'application/octet-stream'
11
+ CONTENT_TYPE_OCTET_STREAM_HEADER = {CONTENT_TYPE_HEADER_KEY: CONTENT_TYPE_OCTET_STREAM}
12
+
13
+ # Multi-part upload
14
+ MULTI_PART_UPLOAD_SIZE_THRESHOLD = 5 * 1024 * 1024 # 5MB in bytes
15
+ MULTI_PART_CHUNK_SIZE = 100 * 1024 * 1024 # 100MB in bytes
@@ -0,0 +1,7 @@
1
+ import enum
2
+
3
+
4
+ @enum.unique
5
+ class EnvType(str, enum.Enum):
6
+ PRODUCTION = 'PRODUCTION'
7
+ PRE_PRODUCTION = 'PRE_PRODUCTION'
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+
5
+
6
+ @enum.unique
7
+ class DeploymentType(str, enum.Enum):
8
+ BASE_CONTAINER = 'BASE_CONTAINER'
9
+ MANUAL = 'MANUAL'
10
+
11
+
12
+ @enum.unique
13
+ class ArtifactType(str, enum.Enum):
14
+ SURROGATE = 'SURROGATE'
15
+ PYTHON_PACKAGE = 'PYTHON_PACKAGE'
@@ -0,0 +1,11 @@
1
+ import enum
2
+
3
+
4
+ @enum.unique
5
+ class ExplainMethod(str, enum.Enum):
6
+ SHAP = 'SHAP'
7
+ FIDDLER_SHAP = 'FIDDLER_SHAP'
8
+ IG = 'IG'
9
+ PERMUTE = 'PERMUTE'
10
+ ZERO_RESET = 'ZERO_RESET'
11
+ MEAN_RESET = 'MEAN_RESET'
@@ -1,3 +1,7 @@
1
+ """Initialize all new entities."""
2
+ from fiddler3.entities.baseline import Baseline # noqa
3
+ from fiddler3.entities.dataset import Dataset # noqa
4
+ from fiddler3.entities.job import Job # noqa
1
5
  from fiddler3.entities.model import Model # noqa
6
+ from fiddler3.entities.model_deployment import ModelDeployment # noqa
2
7
  from fiddler3.entities.project import Project # noqa
3
- from fiddler3.entities.job import Job # noqa
fiddler3/entities/base.py CHANGED
@@ -1,11 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from abc import ABC, abstractmethod
3
4
  from typing import Iterator, TypeVar
4
5
 
5
6
  from requests import Response
6
7
 
7
- from fiddler3 import Connection
8
8
  from fiddler3.configs import DEFAULT_PAGE_SIZE
9
+ from fiddler3.connection import Connection
9
10
  from fiddler3.libs.http_client import RequestClient
10
11
  from fiddler3.schemas.response import PaginatedApiResponse
11
12
 
@@ -14,11 +15,11 @@ BaseEntityType = TypeVar( # pylint: disable=invalid-name
14
15
  )
15
16
 
16
17
 
17
- class BaseEntity:
18
+ class BaseEntity(ABC):
18
19
  @classmethod
19
20
  def _connection(cls) -> Connection:
20
21
  """Fiddler connection instance"""
21
- from fiddler3 import connection
22
+ from fiddler3 import connection # pylint: disable=import-outside-toplevel
22
23
 
23
24
  assert connection is not None
24
25
  return connection
@@ -34,43 +35,46 @@ class BaseEntity:
34
35
  return cls._from_dict(data=response.json()['data'])
35
36
 
36
37
  @classmethod
38
+ @abstractmethod
37
39
  def _from_dict(cls: type[BaseEntityType], data: dict) -> BaseEntityType:
38
40
  """Build entity object from the given dictionary"""
39
- raise NotImplementedError()
41
+
42
+ def _refresh(self, data: dict) -> None: # pylint: disable=unused-argument
43
+ """Refresh the fields of this instance from the given response dictionary"""
44
+ return
45
+
46
+ def _refresh_from_response(self, response: Response) -> None:
47
+ """Refresh the instance from the given response"""
48
+ self._refresh(data=response.json()['data'])
40
49
 
41
50
  @classmethod
42
51
  def _paginate(
43
52
  cls, url: str, params: dict | None = None, page_size: int = DEFAULT_PAGE_SIZE
44
53
  ) -> Iterator[dict]:
45
54
  """
46
- Iterator over pagination API
55
+ Iterate over given pagination endpoint
56
+
47
57
  :param url: Pagination endpoint
48
58
  :param page_size: Number of items per page
49
59
  :return: Iterator of items
50
60
  """
51
61
  offset = 0
52
62
  params = params or {}
53
- params.update(
54
- {
55
- 'limit': page_size,
56
- 'offset': offset,
57
- }
58
- )
63
+ params.update({'limit': page_size})
59
64
 
60
65
  while True:
66
+ params.update({'offset': offset})
61
67
  response = cls._client().get(
62
68
  url=url,
63
- params={
64
- 'limit': page_size,
65
- 'offset': offset,
66
- },
69
+ params=params,
67
70
  )
68
71
  resp_obj = PaginatedApiResponse(**response.json()).data
69
72
 
70
- if resp_obj.page_count == resp_obj.page_index:
71
- break
72
-
73
73
  yield from resp_obj.items
74
74
 
75
+ if resp_obj.page_index >= resp_obj.page_count:
76
+ # Last page
77
+ break
78
+
75
79
  # Update offset
76
80
  offset = resp_obj.offset + page_size