fiddler-client 3.0.0.dev11__tar.gz → 3.1.0.dev1__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 (116) hide show
  1. {fiddler-client-3.0.0.dev11/fiddler_client.egg-info → fiddler-client-3.1.0.dev1}/PKG-INFO +1 -1
  2. fiddler-client-3.1.0.dev1/fiddler/VERSION +1 -0
  3. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/events.py +20 -23
  4. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/model.py +70 -18
  5. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/model.py +1 -0
  6. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_events.py +39 -12
  7. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_generate_model.py +6 -1
  8. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_model.py +115 -6
  9. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_xai.py +2 -0
  10. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/constants.py +1 -0
  11. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1/fiddler_client.egg-info}/PKG-INFO +1 -1
  12. fiddler-client-3.0.0.dev11/fiddler/VERSION +0 -1
  13. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/LICENSE.txt +0 -0
  14. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/MANIFEST.in +0 -0
  15. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/PUBLIC.md +0 -0
  16. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/README.md +0 -0
  17. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/__init__.py +0 -0
  18. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/configs.py +0 -0
  19. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/connection.py +0 -0
  20. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/__init__.py +0 -0
  21. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/alert_rule.py +0 -0
  22. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/baseline.py +0 -0
  23. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/common.py +0 -0
  24. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/dataset.py +0 -0
  25. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/events.py +0 -0
  26. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/job.py +0 -0
  27. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/model.py +0 -0
  28. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/model_deployment.py +0 -0
  29. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/constants/xai.py +0 -0
  30. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/decorators.py +0 -0
  31. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/__init__.py +0 -0
  32. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/alert_record.py +0 -0
  33. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/alert_rule.py +0 -0
  34. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/base.py +0 -0
  35. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/baseline.py +0 -0
  36. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/custom_expression.py +0 -0
  37. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/dataset.py +0 -0
  38. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/file.py +0 -0
  39. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/job.py +0 -0
  40. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/model_artifact.py +0 -0
  41. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/model_deployment.py +0 -0
  42. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/organization.py +0 -0
  43. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/project.py +0 -0
  44. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/surrogate.py +0 -0
  45. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/user.py +0 -0
  46. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/webhook.py +0 -0
  47. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/entities/xai.py +0 -0
  48. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/exceptions.py +0 -0
  49. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/libs/__init__.py +0 -0
  50. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/libs/http_client.py +0 -0
  51. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/libs/json_encoder.py +0 -0
  52. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/libs/semver.py +0 -0
  53. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/packtools/__init__.py +0 -0
  54. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/packtools/gem.py +0 -0
  55. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/packtools/keras_ig_helpers.py +0 -0
  56. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/packtools/project_attributions_helpers.py +0 -0
  57. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/packtools/template_model.py +0 -0
  58. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/__init__.py +0 -0
  59. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/alert_record.py +0 -0
  60. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/alert_rule.py +0 -0
  61. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/base.py +0 -0
  62. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/baseline.py +0 -0
  63. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/custom_expression.py +0 -0
  64. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/custom_features.py +0 -0
  65. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/dataset.py +0 -0
  66. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/events.py +0 -0
  67. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/file.py +0 -0
  68. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/filter_query.py +0 -0
  69. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/job.py +0 -0
  70. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/model_deployment.py +0 -0
  71. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/model_schema.py +0 -0
  72. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/model_spec.py +0 -0
  73. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/model_task_params.py +0 -0
  74. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/organization.py +0 -0
  75. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/project.py +0 -0
  76. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/response.py +0 -0
  77. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/server_info.py +0 -0
  78. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/user.py +0 -0
  79. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/webhook.py +0 -0
  80. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/xai.py +0 -0
  81. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/schemas/xai_params.py +0 -0
  82. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/__init__.py +0 -0
  83. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/__init__.py +0 -0
  84. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_alert_record.py +0 -0
  85. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_alert_rule.py +0 -0
  86. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_baseline.py +0 -0
  87. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_custom_metric.py +0 -0
  88. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_dataset.py +0 -0
  89. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_files.py +0 -0
  90. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_job.py +0 -0
  91. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_mixin.py +0 -0
  92. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_model_artifact.py +0 -0
  93. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_model_deployment.py +0 -0
  94. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_model_surrogate.py +0 -0
  95. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_project.py +0 -0
  96. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_segment.py +0 -0
  97. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/apis/test_webhook.py +0 -0
  98. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/conftest.py +0 -0
  99. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/test_connection.py +0 -0
  100. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/test_json_encoder.py +0 -0
  101. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/test_logger.py +0 -0
  102. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/test_utils.py +0 -0
  103. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/tests/utils.py +0 -0
  104. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/utils/__init__.py +0 -0
  105. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/utils/helpers.py +0 -0
  106. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/utils/logger.py +0 -0
  107. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/utils/model_generator.py +0 -0
  108. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/utils/validations.py +0 -0
  109. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/utils/version.py +0 -0
  110. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler/version.py +0 -0
  111. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler_client.egg-info/SOURCES.txt +0 -0
  112. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler_client.egg-info/dependency_links.txt +0 -0
  113. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler_client.egg-info/requires.txt +0 -0
  114. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/fiddler_client.egg-info/top_level.txt +0 -0
  115. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/setup.cfg +0 -0
  116. {fiddler-client-3.0.0.dev11 → fiddler-client-3.1.0.dev1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fiddler-client
3
- Version: 3.0.0.dev11
3
+ Version: 3.1.0.dev1
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.dev1
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import tempfile
3
4
  from pathlib import Path
4
- from typing import Any, Iterator
5
+ from typing import Any
5
6
  from uuid import UUID
6
7
 
7
8
  import pandas as pd
8
9
  from requests import Response
10
+ from tqdm import tqdm
9
11
 
10
12
  from fiddler.configs import STREAM_EVENT_LIMIT
11
13
  from fiddler.connection import ConnectionMixin
@@ -28,21 +30,6 @@ class EventPublisher(ConnectionMixin):
28
30
  """
29
31
  self.model_id = model_id
30
32
 
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
45
-
46
33
  @handle_api_error
47
34
  def publish(
48
35
  self,
@@ -56,13 +43,13 @@ class EventPublisher(ConnectionMixin):
56
43
 
57
44
  :param source: one of:
58
45
  Path or str path: path for data file.
59
- list[dict]: list of event dicts (max 1000) EnvType.PRE_PRODUCTION is not supported
60
- dataframe: events dataframe. EnvType.PRE_PRODUCTION not supported.
46
+ dataframe: events dataframe.
47
+ list[dict]: list of event dicts EnvType.PRE_PRODUCTION is not supported
61
48
  :param environment: Either EnvType.PRE_PRODUCTION or EnvType.PRODUCTION
62
49
  :param dataset_name: Name of the dataset. Not supported for EnvType.PRODUCTION
63
50
  :param update: flag indicating if the events are updates to previously published rows
64
51
 
65
- :return: list[UUID] for list of dicts or dataframe source and Job object for file path source.
52
+ :return: list[UUID] for list of dicts source and Job object for file path or dataframe source.
66
53
  """
67
54
  if isinstance(source, (str, Path)):
68
55
  return self._publish_file(
@@ -72,20 +59,30 @@ class EventPublisher(ConnectionMixin):
72
59
  update=update,
73
60
  )
74
61
 
75
- if isinstance(source, (list, pd.DataFrame)):
62
+ if isinstance(source, pd.DataFrame):
63
+ with tempfile.NamedTemporaryFile(suffix='.csv') as temp_file:
64
+ source.to_csv(temp_file.name, index=False)
65
+ return self._publish_file(
66
+ source=temp_file.name,
67
+ environment=environment,
68
+ dataset_name=dataset_name,
69
+ update=update,
70
+ )
71
+
72
+ elif isinstance(source, list):
76
73
  return self._publish_stream(source=source, update=update)
77
74
 
78
75
  raise ValueError(f'Unsupported source - {type(source)}')
79
76
 
80
77
  def _publish_stream(
81
78
  self,
82
- source: list[dict[str, Any]] | pd.DataFrame,
79
+ source: list[dict[str, Any]],
83
80
  update: bool = False,
84
81
  ) -> list[UUID]:
85
82
  event_ids = []
86
- for events in self._get_stream_chunks(source=source):
83
+ for i in tqdm(range(0, len(source), self.STREAM_LIMIT)):
87
84
  response = self._publish_call(
88
- source=EventsSource(events=events),
85
+ source=EventsSource(events=source[i : i + self.STREAM_LIMIT]),
89
86
  environment=EnvType.PRODUCTION,
90
87
  dataset_name=None,
91
88
  update=update,
@@ -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,
@@ -22,6 +22,7 @@ class ModelCompactResp(BaseModel):
22
22
  class ModelResp(BaseModel):
23
23
  id: UUID
24
24
  name: str
25
+ version: str
25
26
  input_type: str
26
27
  task: str
27
28
  task_params: ModelTaskParams
@@ -100,52 +100,74 @@ def test_publish_file() -> None:
100
100
 
101
101
 
102
102
  @responses.activate
103
- def test_publish_stream() -> None:
103
+ def test_publish_df() -> None:
104
104
  responses.get(
105
105
  url=f'{URL}/v3/models/{MODEL_ID}',
106
106
  json=MODEL_API_RESPONSE_200,
107
107
  )
108
108
  model = Model.get(id_=MODEL_ID)
109
109
 
110
+ responses.post(
111
+ url=f'{URL}/v3/files/upload',
112
+ json=SINGLE_UPLOAD_200_RESPONSE,
113
+ )
114
+ responses.get(
115
+ url=f'{URL}/v3/jobs/{JOB_ID}',
116
+ json=PUBLISH_JOB_RESPONSE,
117
+ )
118
+
110
119
  responses.post(
111
120
  url=f'{URL}/v3/events',
112
- json=STREAM_PUBLISH_202_API_RESPONSE,
121
+ json=FILE_PUBLISH_202_API_RESPONSE,
113
122
  )
114
123
  publish_response = model.publish(
115
- source=df,
116
- environment=EnvType.PRODUCTION,
124
+ source=df, environment=EnvType.PRE_PRODUCTION, dataset_name='dataset_1'
117
125
  )
118
126
 
119
- assert isinstance(publish_response, list)
120
- assert len(publish_response) == df.shape[0]
127
+ assert isinstance(publish_response, Job)
128
+
129
+ FILE_PUBLISH_202_API_RESPONSE['data']['job']['name'] = 'Publish events'
121
130
 
122
131
  publish_response = model.publish(
123
- source=df.to_dict('records'),
132
+ source=df,
124
133
  environment=EnvType.PRODUCTION,
125
134
  )
126
135
 
127
- assert isinstance(publish_response, list)
128
- assert len(publish_response) == df.shape[0]
136
+ assert isinstance(publish_response, Job)
137
+
138
+ FILE_PUBLISH_202_API_RESPONSE['data']['job']['name'] = 'Publish events'
129
139
 
130
140
 
131
141
  @responses.activate
132
- def test_publish_update() -> None:
142
+ def test_publish_stream() -> None:
133
143
  responses.get(
134
144
  url=f'{URL}/v3/models/{MODEL_ID}',
135
145
  json=MODEL_API_RESPONSE_200,
136
146
  )
137
147
  model = Model.get(id_=MODEL_ID)
138
148
 
139
- responses.patch(
149
+ responses.post(
140
150
  url=f'{URL}/v3/events',
141
151
  json=STREAM_PUBLISH_202_API_RESPONSE,
142
152
  )
153
+
143
154
  publish_response = model.publish(
144
- source=df, environment=EnvType.PRODUCTION, update=True
155
+ source=df.to_dict('records'),
156
+ environment=EnvType.PRODUCTION,
145
157
  )
158
+
146
159
  assert isinstance(publish_response, list)
147
160
  assert len(publish_response) == df.shape[0]
148
161
 
162
+
163
+ @responses.activate
164
+ def test_publish_update() -> None:
165
+ responses.get(
166
+ url=f'{URL}/v3/models/{MODEL_ID}',
167
+ json=MODEL_API_RESPONSE_200,
168
+ )
169
+ model = Model.get(id_=MODEL_ID)
170
+
149
171
  responses.post(
150
172
  url=f'{URL}/v3/files/upload',
151
173
  json=SINGLE_UPLOAD_200_RESPONSE,
@@ -167,3 +189,8 @@ def test_publish_update() -> None:
167
189
  )
168
190
 
169
191
  assert isinstance(publish_response, Job)
192
+
193
+ publish_response = model.publish(
194
+ source=df, environment=EnvType.PRODUCTION, update=True
195
+ )
196
+ assert isinstance(publish_response, Job)
@@ -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
@@ -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.dev11
3
+ Version: 3.1.0.dev1
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-dev11