fiddler-client 3.0.0.dev14__tar.gz → 3.1.0.dev2__tar.gz

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 (117) hide show
  1. {fiddler-client-3.0.0.dev14/fiddler_client.egg-info → fiddler-client-3.1.0.dev2}/PKG-INFO +1 -1
  2. fiddler-client-3.1.0.dev2/fiddler/VERSION +1 -0
  3. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/model.py +76 -19
  4. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/custom_features.py +29 -29
  5. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/model.py +2 -0
  6. fiddler-client-3.1.0.dev2/fiddler/schemas/model_spec.py +32 -0
  7. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_generate_model.py +6 -1
  8. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_mixin.py +4 -1
  9. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_model.py +115 -6
  10. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_xai.py +2 -0
  11. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/constants.py +1 -0
  12. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2/fiddler_client.egg-info}/PKG-INFO +1 -1
  13. fiddler-client-3.0.0.dev14/fiddler/VERSION +0 -1
  14. fiddler-client-3.0.0.dev14/fiddler/schemas/model_spec.py +0 -30
  15. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/LICENSE.txt +0 -0
  16. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/MANIFEST.in +0 -0
  17. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/PUBLIC.md +0 -0
  18. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/README.md +0 -0
  19. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/__init__.py +0 -0
  20. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/configs.py +0 -0
  21. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/connection.py +0 -0
  22. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/__init__.py +0 -0
  23. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/alert_rule.py +0 -0
  24. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/baseline.py +0 -0
  25. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/common.py +0 -0
  26. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/dataset.py +0 -0
  27. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/events.py +0 -0
  28. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/job.py +0 -0
  29. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/model.py +0 -0
  30. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/model_deployment.py +0 -0
  31. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/constants/xai.py +0 -0
  32. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/decorators.py +0 -0
  33. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/__init__.py +0 -0
  34. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/alert_record.py +0 -0
  35. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/alert_rule.py +0 -0
  36. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/base.py +0 -0
  37. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/baseline.py +0 -0
  38. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/custom_expression.py +0 -0
  39. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/dataset.py +0 -0
  40. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/events.py +0 -0
  41. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/file.py +0 -0
  42. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/job.py +0 -0
  43. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/model_artifact.py +0 -0
  44. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/model_deployment.py +0 -0
  45. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/organization.py +0 -0
  46. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/project.py +0 -0
  47. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/surrogate.py +0 -0
  48. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/user.py +0 -0
  49. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/webhook.py +0 -0
  50. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/entities/xai.py +0 -0
  51. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/exceptions.py +0 -0
  52. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/libs/__init__.py +0 -0
  53. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/libs/http_client.py +0 -0
  54. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/libs/json_encoder.py +0 -0
  55. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/libs/semver.py +0 -0
  56. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/packtools/__init__.py +0 -0
  57. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/packtools/gem.py +0 -0
  58. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/packtools/keras_ig_helpers.py +0 -0
  59. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/packtools/project_attributions_helpers.py +0 -0
  60. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/packtools/template_model.py +0 -0
  61. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/__init__.py +0 -0
  62. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/alert_record.py +0 -0
  63. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/alert_rule.py +0 -0
  64. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/base.py +0 -0
  65. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/baseline.py +0 -0
  66. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/custom_expression.py +0 -0
  67. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/dataset.py +0 -0
  68. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/events.py +0 -0
  69. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/file.py +0 -0
  70. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/filter_query.py +0 -0
  71. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/job.py +0 -0
  72. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/model_deployment.py +0 -0
  73. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/model_schema.py +0 -0
  74. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/model_task_params.py +0 -0
  75. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/organization.py +0 -0
  76. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/project.py +0 -0
  77. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/response.py +0 -0
  78. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/server_info.py +0 -0
  79. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/user.py +0 -0
  80. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/webhook.py +0 -0
  81. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/xai.py +0 -0
  82. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/schemas/xai_params.py +0 -0
  83. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/__init__.py +0 -0
  84. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/__init__.py +0 -0
  85. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_alert_record.py +0 -0
  86. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_alert_rule.py +0 -0
  87. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_baseline.py +0 -0
  88. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_custom_metric.py +0 -0
  89. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_dataset.py +0 -0
  90. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_events.py +0 -0
  91. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_files.py +0 -0
  92. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_job.py +0 -0
  93. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_model_artifact.py +0 -0
  94. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_model_deployment.py +0 -0
  95. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_model_surrogate.py +0 -0
  96. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_project.py +0 -0
  97. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_segment.py +0 -0
  98. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/apis/test_webhook.py +0 -0
  99. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/conftest.py +0 -0
  100. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/test_connection.py +0 -0
  101. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/test_json_encoder.py +0 -0
  102. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/test_logger.py +0 -0
  103. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/test_utils.py +0 -0
  104. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/tests/utils.py +0 -0
  105. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/utils/__init__.py +0 -0
  106. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/utils/helpers.py +0 -0
  107. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/utils/logger.py +0 -0
  108. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/utils/model_generator.py +0 -0
  109. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/utils/validations.py +0 -0
  110. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/utils/version.py +0 -0
  111. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler/version.py +0 -0
  112. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler_client.egg-info/SOURCES.txt +0 -0
  113. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler_client.egg-info/dependency_links.txt +0 -0
  114. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler_client.egg-info/requires.txt +0 -0
  115. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/fiddler_client.egg-info/top_level.txt +0 -0
  116. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/setup.cfg +0 -0
  117. {fiddler-client-3.0.0.dev14 → fiddler-client-3.1.0.dev2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fiddler-client
3
- Version: 3.0.0.dev14
3
+ Version: 3.1.0.dev2
4
4
  Summary: Python client for Fiddler Platform
5
5
  Home-page: https://fiddler.ai
6
6
  Author: Fiddler Labs
@@ -0,0 +1 @@
1
+ 3.1.0.dev2
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import builtins
4
+ import copy
4
5
  import typing
5
6
  from dataclasses import dataclass
6
7
  from datetime import datetime
@@ -52,6 +53,7 @@ class Model(
52
53
  project_id: UUID | str,
53
54
  schema: ModelSchema,
54
55
  spec: ModelSpec,
56
+ version: str | None = None,
55
57
  input_type: str = ModelInputType.TABULAR,
56
58
  task: str = ModelTask.NOT_SET,
57
59
  task_params: ModelTaskParams | None = None,
@@ -63,6 +65,7 @@ class Model(
63
65
  ) -> None:
64
66
  """Construct a model instance"""
65
67
  self.name = name
68
+ self.version = version
66
69
  self.project_id = project_id
67
70
  self.schema = schema
68
71
  self.input_type = input_type
@@ -101,6 +104,7 @@ class Model(
101
104
  # Initialize
102
105
  instance = cls(
103
106
  name=resp_obj.name,
107
+ version=resp_obj.version,
104
108
  schema=resp_obj.schema_,
105
109
  spec=resp_obj.spec,
106
110
  project_id=resp_obj.project.id,
@@ -141,6 +145,7 @@ class Model(
141
145
  fields = [
142
146
  'id',
143
147
  'name',
148
+ 'version',
144
149
  'spec',
145
150
  'input_type',
146
151
  'task',
@@ -188,12 +193,20 @@ class Model(
188
193
 
189
194
  @classmethod
190
195
  @handle_api_error
191
- def from_name(cls, name: str, project_id: UUID | str) -> Model:
196
+ def from_name(
197
+ cls,
198
+ name: str,
199
+ project_id: UUID | str,
200
+ version: str | None = None,
201
+ latest: bool = False,
202
+ ) -> Model:
192
203
  """
193
204
  Get the model instance from model name within a project.
194
205
 
195
206
  :param name: Model name
196
207
  :param project_id: Project identifier
208
+ :param version: Version name
209
+ :param latest: Whether to fetch the latest version of the model or first version
197
210
  :return: Model instance
198
211
  """
199
212
  _filter = QueryCondition(
@@ -207,8 +220,15 @@ class Model(
207
220
  ]
208
221
  )
209
222
 
223
+ if version:
224
+ _filter.add_rule(
225
+ QueryRule(field='version', operator=OperatorType.EQUAL, value=version)
226
+ )
227
+
228
+ ordering = '-created_at' if latest else 'created_at'
210
229
  response = cls._client().get(
211
- url=cls._get_url(), params={'filter': _filter.json()}
230
+ url=cls._get_url(),
231
+ params={'filter': _filter.json(), 'limit': 1, 'ordering': ordering},
212
232
  )
213
233
 
214
234
  if response.json()['data']['total'] == 0:
@@ -219,22 +239,27 @@ class Model(
219
239
  @handle_api_error
220
240
  def create(self) -> Model:
221
241
  """Create a new model."""
242
+ payload = {
243
+ 'name': self.name,
244
+ 'project_id': str(self.project_id),
245
+ 'schema': self.schema.dict(),
246
+ 'spec': self.spec.dict(),
247
+ 'input_type': self.input_type,
248
+ 'task': self.task,
249
+ 'task_params': self.task_params.dict(),
250
+ 'description': self.description,
251
+ 'event_id_col': self.event_id_col,
252
+ 'event_ts_col': self.event_ts_col,
253
+ 'event_ts_format': self.event_ts_format,
254
+ 'xai_params': self.xai_params.dict(),
255
+ }
256
+
257
+ if self.version:
258
+ payload['version'] = self.version
259
+
222
260
  response = self._client().post(
223
261
  url=self._get_url(),
224
- data={
225
- 'name': self.name,
226
- 'project_id': str(self.project_id),
227
- 'schema': self.schema.dict(),
228
- 'spec': self.spec.dict(),
229
- 'input_type': self.input_type,
230
- 'task': self.task,
231
- 'task_params': self.task_params.dict(),
232
- 'description': self.description,
233
- 'event_id_col': self.event_id_col,
234
- 'event_ts_col': self.event_ts_col,
235
- 'event_ts_format': self.event_ts_format,
236
- 'xai_params': self.xai_params.dict(),
237
- },
262
+ data=payload,
238
263
  )
239
264
  self._refresh_from_response(response=response)
240
265
  return self
@@ -243,6 +268,7 @@ class Model(
243
268
  def update(self) -> None:
244
269
  """Update an existing model."""
245
270
  body: dict[str, Any] = {
271
+ 'version': self.version,
246
272
  'xai_params': self.xai_params.dict(),
247
273
  'description': self.description,
248
274
  'event_id_col': self.event_id_col,
@@ -256,13 +282,13 @@ class Model(
256
282
  @classmethod
257
283
  @handle_api_error
258
284
  def list(
259
- cls,
260
- project_id: UUID | str,
285
+ cls, project_id: UUID | str, name: str | None = None
261
286
  ) -> Iterator[ModelCompact]:
262
287
  """
263
288
  Get a list of all models with the given filters
264
289
 
265
290
  :param project_id: Project identifier
291
+ :param name: Model name, use this for listing all the versions of a model
266
292
  :return: ModelCompact iterator
267
293
  """
268
294
  _filter = QueryCondition(
@@ -273,11 +299,34 @@ class Model(
273
299
  ]
274
300
  )
275
301
 
302
+ if name:
303
+ _filter.add_rule(
304
+ QueryRule(field='name', operator=OperatorType.EQUAL, value=name)
305
+ )
306
+
276
307
  params = {'filter': _filter.json()}
277
308
 
278
309
  for model in cls._paginate(url=cls._get_url(), params=params):
279
310
  yield ModelCompact(id=model['id'], name=model['name'])
280
311
 
312
+ def duplicate(self, version: str | None = None) -> Model:
313
+ """
314
+ Duplicate the model instance with the given version name.
315
+
316
+ This call will not save the model on server. After making changes to the model
317
+ instance call .create() to add the model version to Fiddler Platform.
318
+
319
+ :param version: Version name for the new instance
320
+ :return: Model instance
321
+ """
322
+ self_copy = copy.deepcopy(self)
323
+ self_copy.id = None
324
+
325
+ if version:
326
+ self_copy.version = version
327
+
328
+ return self_copy
329
+
281
330
  @property
282
331
  def datasets(self) -> Iterator[Dataset]:
283
332
  """Fetch all the datasets of this model"""
@@ -316,6 +365,7 @@ class Model(
316
365
  name: str,
317
366
  project_id: UUID | str,
318
367
  spec: ModelSpec | None = None,
368
+ version: str | None = None,
319
369
  input_type: str = ModelInputType.TABULAR,
320
370
  task: str = ModelTask.NOT_SET,
321
371
  task_params: ModelTaskParams | None = None,
@@ -334,6 +384,7 @@ class Model(
334
384
  :param name: Model name
335
385
  :param project_id: Project identifier
336
386
  :param spec: ModelSpec instance
387
+ :param version: Model version name
337
388
  :param input_type: Model input task type
338
389
  :param task: Model task like Regression, Binary classification etc.
339
390
  :param task_params: Parameters based on the task type
@@ -354,6 +405,7 @@ class Model(
354
405
 
355
406
  return Model(
356
407
  name=name,
408
+ version=version,
357
409
  schema=resp_obj.schema_,
358
410
  spec=resp_obj.spec,
359
411
  project_id=project_id,
@@ -496,6 +548,7 @@ class Model(
496
548
  class ModelCompact:
497
549
  id: UUID
498
550
  name: str
551
+ version: str | None = None
499
552
 
500
553
  def fetch(self) -> Model:
501
554
  """Fetch model instance"""
@@ -513,4 +566,8 @@ class ModelCompactMixin:
513
566
  'response.'
514
567
  )
515
568
 
516
- return ModelCompact(id=response.model.id, name=response.model.name)
569
+ return ModelCompact(
570
+ id=response.model.id,
571
+ name=response.model.name,
572
+ version=response.model.version,
573
+ )
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, List, Optional, Type, TypeVar
1
+ from typing import Any, Dict, List, Optional, Type, TypeVar, Literal
2
2
 
3
3
  from pydantic import BaseModel, validator
4
4
 
@@ -10,18 +10,12 @@ CustomFeatureTypeVar = TypeVar('CustomFeatureTypeVar', bound='CustomFeature')
10
10
 
11
11
  class CustomFeature(BaseModel):
12
12
  name: str
13
- type: CustomFeatureType
14
- n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
15
- centroids: Optional[List] = None
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
13
+ type: Any
22
14
 
23
15
  class Config:
24
16
  allow_mutation = False
17
+ use_enum_values = True
18
+ discriminator = 'type'
25
19
 
26
20
  @classmethod
27
21
  def from_columns(
@@ -53,12 +47,13 @@ class CustomFeature(BaseModel):
53
47
  return_dict: Dict[str, Any] = {
54
48
  'name': self.name,
55
49
  'type': self.type.value,
56
- 'n_clusters': self.n_clusters,
57
50
  }
58
51
  if isinstance(self, Multivariate):
59
52
  return_dict['columns'] = self.columns
53
+ return_dict['n_clusters'] = self.n_clusters
60
54
  elif isinstance(self, VectorFeature):
61
55
  return_dict['column'] = self.column
56
+ return_dict['n_clusters'] = self.n_clusters
62
57
  if isinstance(self, (ImageEmbedding, TextEmbedding)):
63
58
  return_dict['source_column'] = self.source_column
64
59
  if isinstance(self, TextEmbedding):
@@ -74,7 +69,9 @@ class CustomFeature(BaseModel):
74
69
 
75
70
 
76
71
  class Multivariate(CustomFeature):
77
- type: CustomFeatureType = CustomFeatureType.FROM_COLUMNS
72
+ type: Literal['FROM_COLUMNS'] = CustomFeatureType.FROM_COLUMNS.value
73
+ n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
74
+ centroids: Optional[List] = None
78
75
  columns: List[str]
79
76
  monitor_components: bool = False
80
77
 
@@ -84,24 +81,32 @@ class Multivariate(CustomFeature):
84
81
  raise ValueError('Multivariate columns must be greater than 1')
85
82
  return value
86
83
 
84
+ @validator('n_clusters')
85
+ def validate_n_clusters(cls, value: int) -> int: # noqa: N805
86
+ if value < 0:
87
+ raise ValueError('n_clusters must be greater than 0')
88
+ return value
89
+
87
90
 
88
91
  class VectorFeature(CustomFeature):
89
- type: CustomFeatureType = CustomFeatureType.FROM_VECTOR
90
- source_column: Optional[str] = None
92
+ type: Literal['FROM_VECTOR'] = CustomFeatureType.FROM_VECTOR.value
93
+ n_clusters: Optional[int] = DEFAULT_NUM_CLUSTERS
94
+ centroids: Optional[List] = None
91
95
  column: str
92
96
 
97
+ @validator('n_clusters')
98
+ def validate_n_clusters(cls, value: int) -> int: # noqa: N805
99
+ if value < 0:
100
+ raise ValueError('n_clusters must be greater than 0')
101
+ return value
102
+
93
103
 
94
104
  class TextEmbedding(VectorFeature):
95
- type: CustomFeatureType = CustomFeatureType.FROM_TEXT_EMBEDDING
105
+ type: Literal['FROM_TEXT_EMBEDDING'] = CustomFeatureType.FROM_TEXT_EMBEDDING.value # type: ignore
106
+ source_column: str
96
107
  n_tags: Optional[int] = DEFAULT_NUM_TAGS
97
108
  tf_idf: Optional[Dict[str, List]] = None
98
109
 
99
- @validator('source_column')
100
- def validate_source_column(cls, value: str) -> str: # noqa: N805
101
- if value is None:
102
- raise ValueError('source_column must be specified')
103
- return value
104
-
105
110
  @validator('n_tags')
106
111
  def validate_n_tags(cls, value: int) -> int: # noqa: N805
107
112
  if value < 0:
@@ -110,13 +115,8 @@ class TextEmbedding(VectorFeature):
110
115
 
111
116
 
112
117
  class ImageEmbedding(VectorFeature):
113
- type: CustomFeatureType = CustomFeatureType.FROM_IMAGE_EMBEDDING
114
-
115
- @validator('source_column')
116
- def validate_source_column(cls, value: str) -> str: # noqa: N805
117
- if value is None:
118
- raise ValueError('source_column must be specified')
119
- return value
118
+ type: Literal['FROM_IMAGE_EMBEDDING'] = CustomFeatureType.FROM_IMAGE_EMBEDDING.value # type: ignore
119
+ source_column: str
120
120
 
121
121
 
122
122
  class Enrichment(CustomFeature):
@@ -135,7 +135,7 @@ class Enrichment(CustomFeature):
135
135
  """
136
136
 
137
137
  # Setting the feature type to ENRICHMENT
138
- type: CustomFeatureType = CustomFeatureType.ENRICHMENT
138
+ type: Literal['ENRICHMENT'] = CustomFeatureType.ENRICHMENT.value
139
139
 
140
140
  # List of input column names used to generate the enrichment
141
141
  columns: List[str]
@@ -17,11 +17,13 @@ from fiddler.schemas.xai_params import XaiParams
17
17
  class ModelCompactResp(BaseModel):
18
18
  id: UUID
19
19
  name: str
20
+ version: Optional[str]
20
21
 
21
22
 
22
23
  class ModelResp(BaseModel):
23
24
  id: UUID
24
25
  name: str
26
+ version: str
25
27
  input_type: str
26
28
  task: str
27
29
  task_params: ModelTaskParams
@@ -0,0 +1,32 @@
1
+ from typing import List, Union
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from fiddler.schemas.custom_features import Multivariate, VectorFeature, TextEmbedding, ImageEmbedding, Enrichment
6
+
7
+
8
+ class ModelSpec(BaseModel):
9
+ """Model spec defines how model columns are used along with model task"""
10
+
11
+ schema_version: int = 1
12
+ """Schema version"""
13
+
14
+ inputs: List[str] = Field(default_factory=list)
15
+ """Feature columns"""
16
+
17
+ outputs: List[str] = Field(default_factory=list)
18
+ """Prediction columns"""
19
+
20
+ targets: List[str] = Field(default_factory=list)
21
+ """Label columns"""
22
+
23
+ decisions: List[str] = Field(default_factory=list)
24
+ """Decisions columns"""
25
+
26
+ metadata: List[str] = Field(default_factory=list)
27
+ """Metadata columns"""
28
+
29
+ custom_features: List[Union[Multivariate, VectorFeature, TextEmbedding, ImageEmbedding, Enrichment]] = Field(
30
+ default_factory=list
31
+ )
32
+ """Custom feature definitions"""
@@ -182,7 +182,12 @@ def test_generate_model_with_parquet_file() -> None:
182
182
 
183
183
  DF.to_csv(temp_file.name, index=False)
184
184
 
185
+ version = 'v1.0'
185
186
  model = Model.from_data(
186
- source=temp_file.name, name='model1', project_id=PROJECT_ID
187
+ source=temp_file.name,
188
+ name='model1',
189
+ version=version,
190
+ project_id=PROJECT_ID,
187
191
  )
188
192
  assert isinstance(model, Model)
193
+ assert model.version == version
@@ -20,6 +20,7 @@ from fiddler.tests.constants import (
20
20
  DATASET_NAME,
21
21
  MODEL_ID,
22
22
  MODEL_NAME,
23
+ MODEL_VERSION,
23
24
  PROJECT_ID,
24
25
  PROJECT_NAME,
25
26
  USER_EMAIL,
@@ -39,7 +40,9 @@ class DummyEntity(
39
40
  def __init__(self) -> None:
40
41
  self._resp = mock.MagicMock()
41
42
  self._resp.project = ProjectCompactResp(id=PROJECT_ID, name=PROJECT_NAME)
42
- self._resp.model = ModelCompactResp(id=MODEL_ID, name=MODEL_NAME)
43
+ self._resp.model = ModelCompactResp(
44
+ id=MODEL_ID, name=MODEL_NAME, version=MODEL_VERSION
45
+ )
43
46
  self._resp.dataset = DatasetCompactResp(
44
47
  id=DATASET_ID, name=DATASET_NAME, type=EnvType.PRE_PRODUCTION
45
48
  )
@@ -18,6 +18,7 @@ from fiddler.tests.constants import (
18
18
  MODEL_DEPLOYMENT_ID,
19
19
  MODEL_ID,
20
20
  MODEL_NAME,
21
+ MODEL_VERSION,
21
22
  ORG_ID,
22
23
  ORG_NAME,
23
24
  PROJECT_ID,
@@ -153,6 +154,7 @@ API_RESPONSE_200 = {
153
154
  'data': {
154
155
  'id': MODEL_ID,
155
156
  'name': MODEL_NAME,
157
+ 'version': MODEL_VERSION,
156
158
  'project': {
157
159
  'id': PROJECT_ID,
158
160
  'name': PROJECT_NAME,
@@ -308,6 +310,7 @@ def test_add_model_success() -> None:
308
310
  assert isinstance(model, Model)
309
311
  assert model.id == UUID(MODEL_ID)
310
312
  assert model.name == MODEL_NAME
313
+ assert model.version == MODEL_VERSION
311
314
 
312
315
 
313
316
  @responses.activate
@@ -350,7 +353,9 @@ def test_get_model_not_found() -> None:
350
353
  @responses.activate
351
354
  def test_model_from_name_success() -> None:
352
355
  params = {
353
- 'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "bank_churn"}, {"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}]}'
356
+ 'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "bank_churn"}, {"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}]}',
357
+ 'limit': 1,
358
+ 'ordering': 'created_at',
354
359
  }
355
360
  responses.get(
356
361
  url=f'{URL}/v3/models',
@@ -365,6 +370,48 @@ def test_model_from_name_success() -> None:
365
370
  assert isinstance(model, Model)
366
371
 
367
372
 
373
+ @responses.activate
374
+ def test_model_from_name_with_version_success() -> None:
375
+ params = {
376
+ 'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "bank_churn"}, {"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}, {"field": "version", "operator": "equal", "value": "v1"}]}',
377
+ 'limit': 1,
378
+ 'ordering': 'created_at',
379
+ }
380
+ responses.get(
381
+ url=f'{URL}/v3/models',
382
+ json=API_RESPONSE_FROM_NAME,
383
+ match=[matchers.query_param_matcher(params)],
384
+ )
385
+ responses.get(
386
+ url=f'{URL}/v3/models/{MODEL_ID}',
387
+ json=API_RESPONSE_200,
388
+ )
389
+ model = Model.from_name(
390
+ name=MODEL_NAME, version=MODEL_VERSION, project_id=PROJECT_ID
391
+ )
392
+ assert isinstance(model, Model)
393
+
394
+
395
+ @responses.activate
396
+ def test_model_from_name_latest_success() -> None:
397
+ params = {
398
+ 'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "bank_churn"}, {"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}]}',
399
+ 'limit': 1,
400
+ 'ordering': '-created_at',
401
+ }
402
+ responses.get(
403
+ url=f'{URL}/v3/models',
404
+ json=API_RESPONSE_FROM_NAME,
405
+ match=[matchers.query_param_matcher(params)],
406
+ )
407
+ responses.get(
408
+ url=f'{URL}/v3/models/{MODEL_ID}',
409
+ json=API_RESPONSE_200,
410
+ )
411
+ model = Model.from_name(name=MODEL_NAME, project_id=PROJECT_ID, latest=True)
412
+ assert isinstance(model, Model)
413
+
414
+
368
415
  @responses.activate
369
416
  def test_model_from_name_not_found() -> None:
370
417
  resp = API_RESPONSE_FROM_NAME.copy()
@@ -373,7 +420,9 @@ def test_model_from_name_not_found() -> None:
373
420
  resp['data']['items'] = []
374
421
 
375
422
  params = {
376
- 'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "bank_churn"}, {"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}]}'
423
+ 'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "bank_churn"}, {"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}]}',
424
+ 'limit': 1,
425
+ 'ordering': 'created_at',
377
426
  }
378
427
  responses.get(
379
428
  url=f'{URL}/v3/models',
@@ -385,9 +434,35 @@ def test_model_from_name_not_found() -> None:
385
434
  Model.from_name(name=MODEL_NAME, project_id=PROJECT_ID)
386
435
 
387
436
 
437
+ @responses.activate
438
+ def test_model_from_name_with_version_not_found() -> None:
439
+ resp = API_RESPONSE_FROM_NAME.copy()
440
+ resp['data']['total'] = 0
441
+ resp['data']['item_count'] = 0
442
+ resp['data']['items'] = []
443
+
444
+ params = {
445
+ 'filter': '{"condition": "AND", "rules": [{"field": "name", "operator": "equal", "value": "bank_churn"}, {"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}, {"field": "version", "operator": "equal", "value": "v1"}]}',
446
+ 'limit': 1,
447
+ 'ordering': 'created_at',
448
+ }
449
+ responses.get(
450
+ url=f'{URL}/v3/models',
451
+ json=resp,
452
+ match=[matchers.query_param_matcher(params)],
453
+ )
454
+
455
+ with pytest.raises(NotFound):
456
+ Model.from_name(name=MODEL_NAME, version=MODEL_VERSION, project_id=PROJECT_ID)
457
+
458
+
388
459
  @responses.activate
389
460
  def test_model_list_success() -> None:
390
- params = {'project_id': str(PROJECT_ID), 'limit': 50, 'offset': 0}
461
+ params = {
462
+ 'filter': '{"condition": "AND", "rules": [{"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}]}',
463
+ 'limit': 50,
464
+ 'offset': 0,
465
+ }
391
466
  responses.get(
392
467
  url=f'{URL}/v3/models',
393
468
  json=LIST_API_RESPONSE,
@@ -399,9 +474,9 @@ def test_model_list_success() -> None:
399
474
 
400
475
 
401
476
  @responses.activate
402
- def test_model_list_success() -> None:
477
+ def test_model_list_with_name() -> None:
403
478
  params = {
404
- 'filter': '{"condition": "AND", "rules": [{"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}]}',
479
+ 'filter': '{"condition": "AND", "rules": [{"field": "project_id", "operator": "equal", "value": "1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1"}, {"field": "name", "operator": "equal", "value": "bank_churn"}]}',
405
480
  'limit': 50,
406
481
  'offset': 0,
407
482
  }
@@ -410,7 +485,7 @@ def test_model_list_success() -> None:
410
485
  json=LIST_API_RESPONSE,
411
486
  match=[matchers.query_param_matcher(params)],
412
487
  )
413
- models = Model.list(project_id=PROJECT_ID)
488
+ models = Model.list(project_id=PROJECT_ID, name=MODEL_NAME)
414
489
  for model in models:
415
490
  assert isinstance(model, ModelCompact)
416
491
 
@@ -471,6 +546,18 @@ def test_update_model_success(mocker) -> None:
471
546
  model.update()
472
547
  assert model.description == 'Test model update'
473
548
 
549
+ # update version
550
+ new_version = 'v2'
551
+ mocker.patch.dict(API_RESPONSE_200['data'], {'version': new_version})
552
+
553
+ responses.patch(
554
+ url=f'{URL}/v3/models/{MODEL_ID}',
555
+ json=API_RESPONSE_200,
556
+ )
557
+ model.version = new_version
558
+ model.update()
559
+ assert model.version == new_version
560
+
474
561
 
475
562
  @responses.activate
476
563
  def test_update_model_not_found() -> None:
@@ -556,3 +643,25 @@ def test_model_schema_dunder_methods() -> None:
556
643
  # Delete method - KeyError
557
644
  with pytest.raises(KeyError):
558
645
  del schema['foo']
646
+
647
+
648
+ @responses.activate
649
+ def test_model_duplicate() -> None:
650
+ responses.get(
651
+ url=f'{URL}/v3/models/{MODEL_ID}',
652
+ json=API_RESPONSE_200,
653
+ )
654
+ model = Model.get(id_=MODEL_ID)
655
+
656
+ assert model.id == UUID(MODEL_ID)
657
+
658
+ # Without version parameter
659
+ model_copy = model.duplicate()
660
+ assert model_copy.id is None
661
+ assert model_copy.version == model.version
662
+
663
+ # With version parameter
664
+ new_version = 'v2'
665
+ model_copy = model.duplicate(version=new_version)
666
+ assert model_copy.id is None
667
+ assert model_copy.version == new_version
@@ -16,6 +16,7 @@ from fiddler.tests.constants import (
16
16
  JOB_ID,
17
17
  MODEL_ID,
18
18
  MODEL_NAME,
19
+ MODEL_VERSION,
19
20
  ORG_ID,
20
21
  ORG_NAME,
21
22
  OUTPUT_DIR,
@@ -70,6 +71,7 @@ MODEL_API_RESPONSE_200 = {
70
71
  'data': {
71
72
  'id': MODEL_ID,
72
73
  'name': MODEL_NAME,
74
+ 'version': MODEL_VERSION,
73
75
  'project': {
74
76
  'id': PROJECT_ID,
75
77
  'name': PROJECT_NAME,
@@ -10,6 +10,7 @@ PROJECT_NAME = 'bank_churn'
10
10
  PROJECT_ID = '1531bfd9-2ca2-4a7b-bb5a-136c8da09ca1'
11
11
  MODEL_NAME = 'bank_churn'
12
12
  MODEL_ID = '4531bfd9-2ca2-4a7b-bb5a-136c8da09ca2'
13
+ MODEL_VERSION = 'v1'
13
14
  USER_ID = '5531bfd9-2ca2-4a7b-bb5a-136c8da09ca3'
14
15
  USER_NAME = 'foo'
15
16
  USER_EMAIL = 'foo@fiddler.ai'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fiddler-client
3
- Version: 3.0.0.dev14
3
+ Version: 3.1.0.dev2
4
4
  Summary: Python client for Fiddler Platform
5
5
  Home-page: https://fiddler.ai
6
6
  Author: Fiddler Labs
@@ -1 +0,0 @@
1
- 3.0.0.dev14
@@ -1,30 +0,0 @@
1
- from typing import List
2
-
3
- from pydantic import BaseModel
4
-
5
- from fiddler.schemas.custom_features import CustomFeature
6
-
7
-
8
- class ModelSpec(BaseModel):
9
- """Model spec defines how model columns are used along with model task"""
10
-
11
- schema_version: int = 1
12
- """Schema version"""
13
-
14
- inputs: List[str] = []
15
- """Feature columns"""
16
-
17
- outputs: List[str] = []
18
- """Prediction columns"""
19
-
20
- targets: List[str] = []
21
- """Label columns"""
22
-
23
- decisions: List[str] = []
24
- """Decisions columns"""
25
-
26
- metadata: List[str] = []
27
- """Metadata columns"""
28
-
29
- custom_features: List[CustomFeature] = []
30
- """Custom feature definitions"""