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.
Files changed (39) hide show
  1. fiddler/_version.py +1 -1
  2. fiddler/core_objects.py +18 -5
  3. fiddler/schema/alert.py +3 -3
  4. fiddler/schemas/custom_features.py +31 -1
  5. fiddler3/__init__.py +121 -3
  6. fiddler3/configs.py +3 -0
  7. fiddler3/constants/model.py +9 -0
  8. fiddler3/entities/__init__.py +0 -10
  9. fiddler3/entities/alert_rule.py +79 -2
  10. fiddler3/entities/baseline.py +21 -15
  11. fiddler3/entities/dataset.py +1 -1
  12. fiddler3/entities/events.py +68 -52
  13. fiddler3/entities/{files.py → file.py} +7 -15
  14. fiddler3/entities/model.py +81 -13
  15. fiddler3/entities/model_artifact.py +5 -5
  16. fiddler3/entities/model_deployment.py +12 -1
  17. fiddler3/entities/project.py +1 -1
  18. fiddler3/entities/{model_surrogate.py → surrogate.py} +27 -29
  19. fiddler3/entities/xai.py +8 -9
  20. fiddler3/libs/http_client.py +1 -1
  21. fiddler3/schemas/__init__.py +0 -9
  22. fiddler3/schemas/alert_rule.py +7 -0
  23. fiddler3/schemas/custom_features.py +34 -12
  24. fiddler3/schemas/model_deployment.py +5 -4
  25. fiddler3/schemas/webhook.py +0 -3
  26. fiddler3/tests/apis/test_alert_rule.py +71 -1
  27. fiddler3/tests/apis/test_events.py +41 -0
  28. fiddler3/tests/apis/test_files.py +1 -1
  29. fiddler3/tests/apis/test_model.py +5 -1
  30. fiddler3/tests/apis/test_project.py +1 -1
  31. fiddler3/tests/apis/test_webhook.py +0 -23
  32. fiddler3/utils/helpers.py +20 -0
  33. {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/METADATA +15 -5
  34. {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/RECORD +38 -39
  35. fiddler3/entities/helpers.py +0 -21
  36. /fiddler3/schemas/{files.py → file.py} +0 -0
  37. {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/LICENSE.txt +0 -0
  38. {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/WHEEL +0 -0
  39. {fiddler_client-2.5.0.dev1.dist-info → fiddler_client-2.5.0.dev2.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ import io
4
4
  import os
5
5
  from datetime import datetime
6
6
  from pathlib import Path
7
- from typing import Any, Callable
7
+ from typing import Any
8
8
  from uuid import UUID
9
9
 
10
10
  from requests import Response
@@ -14,7 +14,7 @@ from fiddler3.constants.common import MULTI_PART_CHUNK_SIZE
14
14
  from fiddler3.decorators import handle_api_error
15
15
  from fiddler3.entities.base import BaseEntity
16
16
  from fiddler3.entities.user import CreatedByMixin, UpdatedByMixin
17
- from fiddler3.schemas.files import FileResp
17
+ from fiddler3.schemas.file import FileResp
18
18
  from fiddler3.utils.logger import get_logger
19
19
 
20
20
  logger = get_logger(__name__)
@@ -66,16 +66,13 @@ class File(BaseEntity, CreatedByMixin, UpdatedByMixin):
66
66
  chunk_size: int = MULTI_PART_CHUNK_SIZE,
67
67
  ) -> File:
68
68
  """Upload file. Single or multipart upload depends on file size"""
69
- upload_method = self._get_upload_method(chunk_size=chunk_size)
70
- return upload_method(
71
- chunk_size=chunk_size,
72
- )
69
+ if os.path.getsize(self.path) < chunk_size:
70
+ return self.single_upload()
71
+
72
+ return self.multipart_upload(chunk_size=chunk_size)
73
73
 
74
74
  @handle_api_error
75
- def single_upload(
76
- self,
77
- chunk_size: int = MULTI_PART_CHUNK_SIZE, # pylint: disable=unused-argument
78
- ) -> File:
75
+ def single_upload(self) -> File:
79
76
  """Single part file upload"""
80
77
  with open(self.path, 'rb') as f:
81
78
  multipart_data = MultipartEncoder(fields={'file': (self.name, f)})
@@ -163,8 +160,3 @@ class File(BaseEntity, CreatedByMixin, UpdatedByMixin):
163
160
 
164
161
  logger.info('Multi-part upload complete')
165
162
  return response
166
-
167
- def _get_upload_method(self, chunk_size: int) -> Callable:
168
- if os.path.getsize(self.path) < chunk_size:
169
- return self.single_upload
170
- return self.multipart_upload
@@ -11,16 +11,16 @@ from uuid import UUID
11
11
 
12
12
  import pandas as pd
13
13
 
14
+ from fiddler3.constants.dataset import EnvType
14
15
  from fiddler3.constants.model import ModelInputType, ModelTask
15
16
  from fiddler3.decorators import handle_api_error
16
17
  from fiddler3.entities.base import BaseEntity
17
- from fiddler3.entities.events import EventMixin
18
- from fiddler3.entities.helpers import raise_not_found
18
+ from fiddler3.entities.events import EventPublisher
19
19
  from fiddler3.entities.job import Job
20
20
  from fiddler3.entities.model_artifact import ModelArtifact
21
21
  from fiddler3.entities.model_deployment import ModelDeployment
22
- from fiddler3.entities.model_surrogate import SurrogateMixin
23
22
  from fiddler3.entities.project import ProjectCompactMixin
23
+ from fiddler3.entities.surrogate import Surrogate
24
24
  from fiddler3.entities.user import CreatedByMixin, UpdatedByMixin
25
25
  from fiddler3.entities.xai import XaiMixin
26
26
  from fiddler3.schemas.deployment_params import DeploymentParams
@@ -31,6 +31,7 @@ from fiddler3.schemas.model_schema import ModelSchema
31
31
  from fiddler3.schemas.model_spec import ModelSpec
32
32
  from fiddler3.schemas.model_task_params import ModelTaskParams
33
33
  from fiddler3.schemas.xai_params import XaiParams
34
+ from fiddler3.utils.helpers import raise_not_found
34
35
  from fiddler3.utils.model_schema_generator import SchemaGeneratorFactory
35
36
 
36
37
  if typing.TYPE_CHECKING:
@@ -42,16 +43,14 @@ class Model(
42
43
  CreatedByMixin,
43
44
  ProjectCompactMixin,
44
45
  UpdatedByMixin,
45
- SurrogateMixin,
46
46
  XaiMixin,
47
- EventMixin,
48
47
  ): # pylint: disable=too-many-ancestors
49
48
  def __init__( # pylint: disable=too-many-arguments
50
49
  self,
51
50
  name: str,
52
51
  project_id: UUID,
53
52
  schema: ModelSchema,
54
- spec: ModelSpec | None = None,
53
+ spec: ModelSpec,
55
54
  input_type: str = ModelInputType.TABULAR,
56
55
  task: str = ModelTask.NOT_SET,
57
56
  task_params: ModelTaskParams | None = None,
@@ -71,7 +70,7 @@ class Model(
71
70
  self.event_id_col = event_id_col
72
71
  self.event_ts_col = event_ts_col
73
72
  self.event_ts_format = event_ts_format
74
- self.spec = spec or ModelSpec()
73
+ self.spec = spec
75
74
  self.task_params = task_params or ModelTaskParams()
76
75
  self.xai_params = xai_params or XaiParams()
77
76
 
@@ -167,6 +166,18 @@ class Model(
167
166
  assert self.id is not None
168
167
  return ModelArtifact(model_id=self.id)
169
168
 
169
+ @cached_property
170
+ def _surrogate(self) -> Surrogate:
171
+ """Model artifact instance"""
172
+ assert self.id is not None
173
+ return Surrogate(model_id=self.id)
174
+
175
+ @cached_property
176
+ def _event_publisher(self) -> EventPublisher:
177
+ """Event publisher instance"""
178
+ assert self.id is not None
179
+ return EventPublisher(model_id=self.id)
180
+
170
181
  @classmethod
171
182
  @handle_api_error
172
183
  def get(cls, id_: UUID | str) -> Model:
@@ -196,7 +207,7 @@ class Model(
196
207
  if response.json()['data']['total'] == 0:
197
208
  raise_not_found('Model not found for the given identifier')
198
209
 
199
- return cls._from_dict(data=response.json()['data']['items'][0])
210
+ return cls.get(id_=response.json()['data']['items'][0]['id'])
200
211
 
201
212
  @handle_api_error
202
213
  def create(self) -> Model:
@@ -267,11 +278,9 @@ class Model(
267
278
  @handle_api_error
268
279
  def model_deployment(self) -> ModelDeployment:
269
280
  """Fetch all the deployments for this model"""
270
- url = f'{self._get_url(id_=self.id)}/deployment'
271
- response = self._client().get(url=url)
272
- return ModelDeployment._from_dict( # pylint: disable=protected-access
273
- data=response.json()['data']
274
- )
281
+ assert self.id is not None
282
+
283
+ return ModelDeployment.of(model_id=self.id)
275
284
 
276
285
  @classmethod
277
286
  @handle_api_error
@@ -309,6 +318,38 @@ class Model(
309
318
  job_compact = JobCompactResp(**response.json()['data']['job'])
310
319
  return Job.get(id_=job_compact.id)
311
320
 
321
+ @handle_api_error
322
+ def publish(
323
+ self,
324
+ source: builtins.list[dict[str, Any]] | str | Path | pd.DataFrame,
325
+ environment: EnvType = EnvType.PRODUCTION,
326
+ dataset_name: str | None = None,
327
+ update: bool = False,
328
+ ) -> builtins.list[UUID] | Job:
329
+ """
330
+ Publish Pre-production or Production data
331
+
332
+ :param source: one of:
333
+ Path or str path: path for data file.
334
+ list[dict]: list of events (max 1000) PRE_PRODUCTION environment is not
335
+ supported
336
+ dataframe: events dataframe. EnvType.PRE_PRODUCTION not supported.
337
+ :param environment: Either EnvType.PRE_PRODUCTION or EnvType.PRODUCTION
338
+ :param dataset_name: Name of the dataset. Not supported for EnvType.PRODUCTION
339
+ :param update: flag indicating if the events are updates to previously
340
+ published rows
341
+
342
+ :return: list[UUID] for list of dicts or dataframe source and Job object for
343
+ file path source.
344
+ """
345
+
346
+ return self._event_publisher.publish(
347
+ source=source,
348
+ environment=environment,
349
+ dataset_name=dataset_name,
350
+ update=update,
351
+ )
352
+
312
353
  def add_artifact(
313
354
  self,
314
355
  model_dir: str | Path,
@@ -353,6 +394,33 @@ class Model(
353
394
 
354
395
  self._artifact.download(output_dir=output_dir)
355
396
 
397
+ def add_surrogate(
398
+ self,
399
+ deployment_params: DeploymentParams | None = None,
400
+ ) -> Job:
401
+ """
402
+ Add a new surrogate model
403
+
404
+ :param deployment_params: Model deployment parameters
405
+ :return: Async job
406
+ """
407
+ job = self._surrogate.add(deployment_params=deployment_params)
408
+ return job
409
+
410
+ @handle_api_error
411
+ def update_surrogate(
412
+ self,
413
+ deployment_params: DeploymentParams | None = None,
414
+ ) -> Job:
415
+ """
416
+ Update an existing surrogate model
417
+
418
+ :param deployment_params: Model deployment parameters
419
+ :return: Async job
420
+ """
421
+ job = self._surrogate.update(deployment_params=deployment_params)
422
+ return job
423
+
356
424
 
357
425
  @dataclass
358
426
  class ModelCompact:
@@ -9,7 +9,7 @@ from uuid import UUID
9
9
 
10
10
  from fiddler3.connection import ConnectionMixin
11
11
  from fiddler3.decorators import handle_api_error
12
- from fiddler3.entities.files import File
12
+ from fiddler3.entities.file import File
13
13
  from fiddler3.entities.job import Job
14
14
  from fiddler3.schemas.deployment_params import DeploymentParams
15
15
  from fiddler3.schemas.job import JobCompactResp
@@ -28,7 +28,7 @@ class ModelArtifact(ConnectionMixin):
28
28
  """
29
29
  self.model_id = model_id
30
30
 
31
- def _get_method(self, update: bool = False) -> Callable:
31
+ def _get_http_method(self, update: bool = False) -> Callable:
32
32
  """Get HTTP method based on operation"""
33
33
  if update:
34
34
  return self._client().put
@@ -60,7 +60,7 @@ class ModelArtifact(ConnectionMixin):
60
60
  )
61
61
  file_path = shutil.make_archive(
62
62
  base_name=str(Path(tmp) / 'files'),
63
- format='tar',
63
+ format='gztar',
64
64
  root_dir=str(model_dir),
65
65
  base_dir='.',
66
66
  )
@@ -97,7 +97,7 @@ class ModelArtifact(ConnectionMixin):
97
97
  update: bool = False,
98
98
  ) -> Job:
99
99
  """Artifact deploy base method."""
100
- method = self._get_method(update)
100
+ http_method = self._get_http_method(update)
101
101
  payload = {
102
102
  'file_id': file_id,
103
103
  'deployment_params': deployment_params.dict(exclude_unset=True)
@@ -105,7 +105,7 @@ class ModelArtifact(ConnectionMixin):
105
105
  else {},
106
106
  }
107
107
 
108
- response = method(
108
+ response = http_method(
109
109
  url=f'/v3/models/{self.model_id}/deploy-artifact',
110
110
  data=payload,
111
111
  )
@@ -37,7 +37,7 @@ class ModelDeployment(
37
37
  self._resp: ModelDeploymentResponse | None = None
38
38
 
39
39
  @staticmethod
40
- def _get_url(model_id: UUID | str | None = None) -> str:
40
+ def _get_url(model_id: UUID | str) -> str:
41
41
  """Get model deployment url."""
42
42
  return f'v3/models/{model_id}/deployment'
43
43
 
@@ -110,3 +110,14 @@ class ModelDeployment(
110
110
  url=self._get_url(model_id=self.model_id), data=body
111
111
  )
112
112
  self._refresh_from_response(response=response)
113
+
114
+ @classmethod
115
+ def of(cls, model_id: UUID) -> ModelDeployment:
116
+ """
117
+ Get model deployment instance of the given model
118
+
119
+ :param model_id: Model identifier
120
+ :return: ModelDeployment instance
121
+ """
122
+ response = cls._client().get(url=cls._get_url(model_id=model_id))
123
+ return cls._from_dict(data=response.json()['data'])
@@ -8,9 +8,9 @@ from uuid import UUID
8
8
 
9
9
  from fiddler3.decorators import handle_api_error
10
10
  from fiddler3.entities.base import BaseEntity
11
- from fiddler3.entities.helpers import raise_not_found
12
11
  from fiddler3.schemas.filter_query import OperatorType, QueryCondition, QueryRule
13
12
  from fiddler3.schemas.project import ProjectResp
13
+ from fiddler3.utils.helpers import raise_not_found
14
14
 
15
15
  if typing.TYPE_CHECKING:
16
16
  from fiddler3.entities.model import ModelCompact
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Callable
3
+ from typing import Any
4
4
  from uuid import UUID
5
5
 
6
+ from fiddler3.connection import ConnectionMixin
6
7
  from fiddler3.decorators import handle_api_error
7
8
  from fiddler3.entities.job import Job
8
9
  from fiddler3.schemas.deployment_params import ArtifactType, DeploymentParams
@@ -12,28 +13,26 @@ from fiddler3.utils.logger import get_logger
12
13
  logger = get_logger(__name__)
13
14
 
14
15
 
15
- class SurrogateMixin:
16
- id: UUID | None
17
- _client: Callable
16
+ class Surrogate(ConnectionMixin):
17
+ def __init__(self, model_id: UUID) -> None:
18
+ """
19
+ Surrogate model methods
18
20
 
19
- def _check_id_attributes(self) -> None:
20
- if not self.id:
21
- raise AttributeError(
22
- 'This method is available only for model object generated from '
23
- 'API response.'
24
- )
21
+ :param model_id: Model identifier
22
+ """
23
+ self.model_id = model_id
25
24
 
26
- def _deploy_surrogate_model(
25
+ def _deploy(
27
26
  self,
28
27
  deployment_params: DeploymentParams | None = None,
29
28
  update: bool = False,
30
29
  ) -> Job:
31
30
  """
32
- Add surrogate model to an existing model
31
+ Deploy surrogate model to an existing model
33
32
 
34
33
  :param deployment_params: Model deployment parameters
35
34
  :param update: Set True for re-generating surrogate model, otherwise False
36
- :return: Async job uuid
35
+ :return: Async job
37
36
  """
38
37
  payload: dict[str, Any] = {}
39
38
 
@@ -43,22 +42,27 @@ class SurrogateMixin:
43
42
  {'deployment_params': deployment_params.dict(exclude_unset=True)}
44
43
  )
45
44
 
46
- url = f'/v3/models/{self.id}/deploy-surrogate'
47
- method: Callable = self._client().put if update else self._client().post
48
- response = method(url=url, data=payload)
45
+ url = f'/v3/models/{self.model_id}/deploy-surrogate'
46
+
47
+ if update:
48
+ http_method = self._client().put
49
+ else:
50
+ http_method = self._client().post
51
+
52
+ response = http_method(url=url, data=payload)
49
53
 
50
54
  job_compact = JobCompactResp(**response.json()['data']['job'])
51
55
 
52
56
  logger.info(
53
57
  'Model[%s] - Submitted job (%s) for deploying a surrogate model',
54
- self.id,
58
+ self.model_id,
55
59
  job_compact.id,
56
60
  )
57
61
 
58
62
  return Job.get(id_=job_compact.id)
59
63
 
60
64
  @handle_api_error
61
- def add_surrogate(
65
+ def add(
62
66
  self,
63
67
  deployment_params: DeploymentParams | None = None,
64
68
  ) -> Job:
@@ -66,16 +70,13 @@ class SurrogateMixin:
66
70
  Add a new surrogate.
67
71
 
68
72
  :param deployment_params: Model deployment parameters
69
- :return: Async job.
73
+ :return: Async job
70
74
  """
71
- self._check_id_attributes()
72
- job = self._deploy_surrogate_model(
73
- deployment_params=deployment_params, update=False
74
- )
75
+ job = self._deploy(deployment_params=deployment_params, update=False)
75
76
  return job
76
77
 
77
78
  @handle_api_error
78
- def update_surrogate(
79
+ def update(
79
80
  self,
80
81
  deployment_params: DeploymentParams | None = None,
81
82
  ) -> Job:
@@ -83,10 +84,7 @@ class SurrogateMixin:
83
84
  Update an existing surrogate.
84
85
 
85
86
  :param deployment_params: Model deployment parameters
86
- :return: Async job.
87
+ :return: Async job
87
88
  """
88
- self._check_id_attributes()
89
- job = self._deploy_surrogate_model(
90
- deployment_params=deployment_params, update=True
91
- )
89
+ job = self._deploy(deployment_params=deployment_params, update=True)
92
90
  return job
fiddler3/entities/xai.py CHANGED
@@ -74,7 +74,7 @@ class XaiMixin:
74
74
  'explanation_type': method,
75
75
  }
76
76
  if ref_data_source:
77
- payload['ref_data_source'] = ref_data_source.dict()
77
+ payload['ref_data_source'] = ref_data_source.dict(exclude_none=True)
78
78
  if num_permutations:
79
79
  payload['num_permutations'] = num_permutations
80
80
  if ci_level:
@@ -114,7 +114,7 @@ class XaiMixin:
114
114
 
115
115
  payload: dict[str, Any] = {
116
116
  'model_id': self.id,
117
- 'data_source': data_source.dict(),
117
+ 'data_source': data_source.dict(exclude_none=True),
118
118
  'protected_features': protected_features,
119
119
  'positive_outcome': positive_outcome,
120
120
  }
@@ -193,8 +193,8 @@ class XaiMixin:
193
193
  """
194
194
  self._check_id_attributes()
195
195
  output_dir = Path(output_dir)
196
- if output_dir.exists():
197
- raise ValueError(f'Output dir already exists {output_dir}')
196
+ if not output_dir.exists():
197
+ os.makedirs(output_dir)
198
198
  payload: dict[str, Any] = {
199
199
  'model_id': self.id,
200
200
  'query': query,
@@ -205,7 +205,6 @@ class XaiMixin:
205
205
  if columns:
206
206
  payload['columns'] = columns
207
207
 
208
- os.makedirs(output_dir)
209
208
  file_path = os.path.join(output_dir, 'output.parquet')
210
209
  with self._client().post(url='/v3/slice-query/download', data=payload) as resp:
211
210
  # Download parquet file
@@ -314,7 +313,7 @@ class XaiMixin:
314
313
 
315
314
  payload: dict[str, Any] = {
316
315
  'model_id': self.id,
317
- 'data_source': data_source.dict(),
316
+ 'data_source': data_source.dict(exclude_none=True),
318
317
  }
319
318
 
320
319
  if num_refs:
@@ -431,7 +430,7 @@ class XaiMixin:
431
430
 
432
431
  response = method(
433
432
  url='/v3/analytics/precompute-feature-impact',
434
- params=payload,
433
+ data=payload,
435
434
  )
436
435
 
437
436
  job_compact = JobCompactResp(**response.json()['data']['job'])
@@ -487,7 +486,7 @@ class XaiMixin:
487
486
 
488
487
  response = method(
489
488
  url='/v3/analytics/precompute-feature-importance',
490
- params=payload,
489
+ data=payload,
491
490
  )
492
491
 
493
492
  job_compact = JobCompactResp(**response.json()['data']['job'])
@@ -556,7 +555,7 @@ class XaiMixin:
556
555
 
557
556
  response = method(
558
557
  url='/v3/analytics/precompute-predictions',
559
- params=payload,
558
+ data=payload,
560
559
  )
561
560
 
562
561
  job_compact = JobCompactResp(**response.json()['data']['job'])
@@ -78,7 +78,7 @@ class RequestClient:
78
78
  request_headers.update(headers)
79
79
 
80
80
  content_type = request_headers.get('Content-Type')
81
- if data and content_type == JSON_CONTENT_TYPE:
81
+ if data is not None and content_type == JSON_CONTENT_TYPE:
82
82
  data = simplejson.dumps(data, ignore_nan=True, cls=RequestClientJSONEncoder) # type: ignore
83
83
 
84
84
  kwargs.setdefault('allow_redirects', True)
@@ -1,9 +0,0 @@
1
- """Initialize all schemas."""
2
- from fiddler3.schemas.custom_features import * # noqa
3
- from fiddler3.schemas.dataset import EnvType # noqa
4
- from fiddler3.schemas.deployment_params import DeploymentParams # noqa
5
- from fiddler3.schemas.model_schema import ModelSchema # noqa
6
- from fiddler3.schemas.model_spec import ModelSpec # noqa
7
- from fiddler3.schemas.model_task_params import ModelTaskParams # noqa
8
- from fiddler3.schemas.xai import * # noqa
9
- from fiddler3.schemas.xai_params import XaiParams # noqa
@@ -30,3 +30,10 @@ class AlertRuleResp(BaseModel):
30
30
 
31
31
  created_at: datetime
32
32
  updated_at: datetime = Field(alias='last_updated')
33
+
34
+
35
+ class NotificationConfig(BaseModel):
36
+ emails: Optional[List[str]]
37
+ pagerduty_services: Optional[List[str]]
38
+ pagerduty_severity: Optional[str]
39
+ webhooks: Optional[List[UUID]]
@@ -1,19 +1,11 @@
1
- from enum import Enum
2
1
  from typing import Any, Dict, List, Optional, Type, TypeVar
3
2
 
4
- from pydantic import BaseModel
5
-
6
- CustomFeatureTypeVar = TypeVar('CustomFeatureTypeVar', bound='CustomFeature')
7
- DEFAULT_NUM_CLUSTERS = 5
8
- DEFAULT_NUM_TAGS = 5
3
+ from pydantic import BaseModel, validator
9
4
 
5
+ from fiddler3.configs import DEFAULT_NUM_CLUSTERS, DEFAULT_NUM_TAGS
6
+ from fiddler3.constants.model import CustomFeatureType
10
7
 
11
- class CustomFeatureType(str, Enum):
12
- FROM_COLUMNS = 'FROM_COLUMNS'
13
- FROM_VECTOR = 'FROM_VECTOR'
14
- FROM_TEXT_EMBEDDING = 'FROM_TEXT_EMBEDDING'
15
- FROM_IMAGE_EMBEDDING = 'FROM_IMAGE_EMBEDDING'
16
- ENRICHMENT = 'ENRICHMENT'
8
+ CustomFeatureTypeVar = TypeVar('CustomFeatureTypeVar', bound='CustomFeature')
17
9
 
18
10
 
19
11
  class CustomFeature(BaseModel):
@@ -22,6 +14,12 @@ class CustomFeature(BaseModel):
22
14
  n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
23
15
  centroids: Optional[List] = None
24
16
 
17
+ @validator('n_clusters')
18
+ def validate_n_clusters(cls, value: int) -> int: # noqa: N805
19
+ if value < 0:
20
+ raise ValueError('n_clusters must be greater than 0')
21
+ return value
22
+
25
23
  class Config:
26
24
  allow_mutation = False
27
25
 
@@ -80,6 +78,12 @@ class Multivariate(CustomFeature):
80
78
  columns: List[str]
81
79
  monitor_components: bool = False
82
80
 
81
+ @validator('columns')
82
+ def validate_columns(cls, value: List[str]) -> List[str]: # noqa: N805
83
+ if len(value) < 2:
84
+ raise ValueError('Multivariate columns must be greater than 1')
85
+ return value
86
+
83
87
 
84
88
  class VectorFeature(CustomFeature):
85
89
  type: CustomFeatureType = CustomFeatureType.FROM_VECTOR
@@ -91,10 +95,28 @@ class TextEmbedding(VectorFeature):
91
95
  type: CustomFeatureType = CustomFeatureType.FROM_TEXT_EMBEDDING
92
96
  n_tags: Optional[int] = DEFAULT_NUM_TAGS
93
97
 
98
+ @validator('source_column')
99
+ def validate_source_column(cls, value: str) -> str: # noqa: N805
100
+ if value is None:
101
+ raise ValueError('source_column must be specified')
102
+ return value
103
+
104
+ @validator('n_tags')
105
+ def validate_n_tags(cls, value: int) -> int: # noqa: N805
106
+ if value < 0:
107
+ raise ValueError('n_tags must be greater than 0')
108
+ return value
109
+
94
110
 
95
111
  class ImageEmbedding(VectorFeature):
96
112
  type: CustomFeatureType = CustomFeatureType.FROM_IMAGE_EMBEDDING
97
113
 
114
+ @validator('source_column')
115
+ def validate_source_column(cls, value: str) -> str: # noqa: N805
116
+ if value is None:
117
+ raise ValueError('source_column must be specified')
118
+ return value
119
+
98
120
 
99
121
  class Enrichment(CustomFeature):
100
122
  """
@@ -1,4 +1,5 @@
1
1
  from datetime import datetime
2
+ from typing import Optional
2
3
  from uuid import UUID
3
4
 
4
5
  from fiddler3.schemas.base import BaseModel
@@ -16,10 +17,10 @@ class ModelDeploymentResponse(BaseModel):
16
17
  artifact_type: str
17
18
  deployment_type: str
18
19
  active: bool
19
- image_uri: str
20
- replicas: int
21
- cpu: int
22
- memory: int
20
+ image_uri: Optional[str]
21
+ replicas: Optional[int]
22
+ cpu: Optional[int]
23
+ memory: Optional[int]
23
24
  created_at: datetime
24
25
  updated_at: datetime
25
26
  created_by: UserCompactResp
@@ -5,7 +5,6 @@ from uuid import UUID
5
5
  from pydantic.fields import Field
6
6
 
7
7
  from fiddler3.schemas.base import BaseModel
8
- from fiddler3.schemas.user import UserCompactResp
9
8
 
10
9
 
11
10
  @enum.unique
@@ -22,5 +21,3 @@ class WebhookResp(BaseModel):
22
21
  provider: WebhookProvider
23
22
  created_at: datetime
24
23
  updated_at: datetime
25
- created_by: UserCompactResp
26
- updated_by: UserCompactResp