fiddler-client 3.0.0.dev11__py3-none-any.whl → 3.1.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fiddler/VERSION +1 -1
- fiddler/entities/events.py +20 -23
- fiddler/entities/model.py +70 -18
- fiddler/schemas/model.py +1 -0
- fiddler/tests/apis/test_events.py +39 -12
- fiddler/tests/apis/test_generate_model.py +6 -1
- fiddler/tests/apis/test_model.py +115 -6
- fiddler/tests/apis/test_xai.py +2 -0
- fiddler/tests/constants.py +1 -0
- {fiddler_client-3.0.0.dev11.dist-info → fiddler_client-3.1.0.dev1.dist-info}/METADATA +1 -1
- {fiddler_client-3.0.0.dev11.dist-info → fiddler_client-3.1.0.dev1.dist-info}/RECORD +14 -14
- {fiddler_client-3.0.0.dev11.dist-info → fiddler_client-3.1.0.dev1.dist-info}/LICENSE.txt +0 -0
- {fiddler_client-3.0.0.dev11.dist-info → fiddler_client-3.1.0.dev1.dist-info}/WHEEL +0 -0
- {fiddler_client-3.0.0.dev11.dist-info → fiddler_client-3.1.0.dev1.dist-info}/top_level.txt +0 -0
fiddler/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0.
|
|
1
|
+
3.1.0.dev1
|
fiddler/entities/events.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
60
|
-
|
|
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
|
|
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,
|
|
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]]
|
|
79
|
+
source: list[dict[str, Any]],
|
|
83
80
|
update: bool = False,
|
|
84
81
|
) -> list[UUID]:
|
|
85
82
|
event_ids = []
|
|
86
|
-
for
|
|
83
|
+
for i in tqdm(range(0, len(source), self.STREAM_LIMIT)):
|
|
87
84
|
response = self._publish_call(
|
|
88
|
-
source=EventsSource(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,
|
fiddler/entities/model.py
CHANGED
|
@@ -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(
|
|
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(),
|
|
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,
|
fiddler/schemas/model.py
CHANGED
|
@@ -100,52 +100,74 @@ def test_publish_file() -> None:
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
@responses.activate
|
|
103
|
-
def
|
|
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=
|
|
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,
|
|
120
|
-
|
|
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
|
|
132
|
+
source=df,
|
|
124
133
|
environment=EnvType.PRODUCTION,
|
|
125
134
|
)
|
|
126
135
|
|
|
127
|
-
assert isinstance(publish_response,
|
|
128
|
-
|
|
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
|
|
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.
|
|
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
|
|
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,
|
|
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
|
fiddler/tests/apis/test_model.py
CHANGED
|
@@ -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 = {
|
|
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
|
|
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
|
fiddler/tests/apis/test_xai.py
CHANGED
|
@@ -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,
|
fiddler/tests/constants.py
CHANGED
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
fiddler/VERSION,sha256=
|
|
1
|
+
fiddler/VERSION,sha256=OSg_m1-GiMUFyiK5ruWqv__EjzZJf10D747c9UNhJwA,11
|
|
2
2
|
fiddler/__init__.py,sha256=U9g19CmjOoli4bMAcD_LD-qTmFFBG3uCCIFm34vcTac,3757
|
|
3
3
|
fiddler/configs.py,sha256=ZimSo0Gk7j1BFkjDHdRdycrGZ8oh-7G5TBXYiOh1HvU,217
|
|
4
4
|
fiddler/connection.py,sha256=fLdSXpu5dwoK-h4Vtc9nbdKENOJ7jfEmrnDhVAtChck,5677
|
|
@@ -22,10 +22,10 @@ fiddler/entities/base.py,sha256=YCTLt1ta8UbD7u-5BW7v_ocA4S7LevEncI5XPS_SB60,2106
|
|
|
22
22
|
fiddler/entities/baseline.py,sha256=k3D-K8WC5MkHDILyejRaH1v9jdCIymT96McDBOVQ9A0,7907
|
|
23
23
|
fiddler/entities/custom_expression.py,sha256=SdS61JiI0gOxWwHAGxkHh1vqS5XsRtyeNSDs5E-Sg2U,6568
|
|
24
24
|
fiddler/entities/dataset.py,sha256=kwD1pZMG6DQGIjejU7A2KUUBRgOi0o1qbbUX-PSlnGg,4399
|
|
25
|
-
fiddler/entities/events.py,sha256=
|
|
25
|
+
fiddler/entities/events.py,sha256=SGA4GFmEzS9U7osodTNslfyoXhJe0j_GgyGmgkCXluk,4291
|
|
26
26
|
fiddler/entities/file.py,sha256=dk8ISfjy0--F2eF23Ae8vGiEZmJW6e9LMqNmKfrY2mE,4710
|
|
27
27
|
fiddler/entities/job.py,sha256=Feiqo6ns4bRcp2uaV-kuGYAKs90-aYXvzqqi1yLivYE,5220
|
|
28
|
-
fiddler/entities/model.py,sha256=
|
|
28
|
+
fiddler/entities/model.py,sha256=J9jb-5BH_DWSyyOoFW9yT45K4BBAvTRcY8G5vgoQiT8,18503
|
|
29
29
|
fiddler/entities/model_artifact.py,sha256=Y7o1mFs-nVDz7HI9mVBu9ICwkqGD_QbT3q6vf8KKesk,5520
|
|
30
30
|
fiddler/entities/model_deployment.py,sha256=mxEsMYiUO-m9X1ND7UuJ1b3LgEcyvOM5PMEmRvOuftk,3769
|
|
31
31
|
fiddler/entities/organization.py,sha256=XvUAdBUOYXzdmblxIs4hi0mAg45NtAwy5DBaMiqi47o,1074
|
|
@@ -55,7 +55,7 @@ fiddler/schemas/events.py,sha256=dH6SGPBO_rgGZetIhSo4Fq0L1pTU7TEKR7zW7gg3fnI,409
|
|
|
55
55
|
fiddler/schemas/file.py,sha256=z_7bOxKbwQsgI8F7MMQvC6RE5dgdzYCMyD5XcBirjCc,462
|
|
56
56
|
fiddler/schemas/filter_query.py,sha256=XP8SoFGSJOo9yf3fA9dPqpKdCJuaUpbKbYixn46lBF0,1325
|
|
57
57
|
fiddler/schemas/job.py,sha256=6q3SiHp43ah0R0eIO77DNgJRug-dejI13NZAd_7c0sk,354
|
|
58
|
-
fiddler/schemas/model.py,sha256=
|
|
58
|
+
fiddler/schemas/model.py,sha256=mGMTszQL-VpcRl9yXsYdN5UnTXuCA0B8rvmyxktNUZs,1378
|
|
59
59
|
fiddler/schemas/model_deployment.py,sha256=s1moOBT7JIMWgGZ4ALxgZJhB4ulVK6i2VkNp7_mOJKo,1128
|
|
60
60
|
fiddler/schemas/model_schema.py,sha256=nagwbYFoWG6CzBTYYYuQNMpGfA6i9da_WPa8uO2IBCo,1914
|
|
61
61
|
fiddler/schemas/model_spec.py,sha256=RiZd-efabsJVfHCI5kXRs_6RrWj4xZB6vqajHSDqxgc,642
|
|
@@ -70,7 +70,7 @@ fiddler/schemas/xai.py,sha256=AZbjjBlTDZbQGj3U-pTM1UX_LJltjy__2t-kM2v5FuY,739
|
|
|
70
70
|
fiddler/schemas/xai_params.py,sha256=7WbUCBojzZckjOA2NUxnk5y3M2WKKXclSzkt5HBfyUU,313
|
|
71
71
|
fiddler/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
72
|
fiddler/tests/conftest.py,sha256=Dmz9FKAFHYdq1AWmcY7NES9pU4cbY9qKWQiojjaX_0E,840
|
|
73
|
-
fiddler/tests/constants.py,sha256
|
|
73
|
+
fiddler/tests/constants.py,sha256=X0UNIfj47HsTBaThMdxZqi3boY_qam25SMrrcdXr-4M,1102
|
|
74
74
|
fiddler/tests/test_connection.py,sha256=d8WoB0QRZEObWfeRAZRXRm1kw9wfEysmeo4Bld7x8js,1865
|
|
75
75
|
fiddler/tests/test_json_encoder.py,sha256=GJeFT5tTA4RTfZn1q2xm1V5SEj5DNsW3LeHPVfFxcFw,783
|
|
76
76
|
fiddler/tests/test_logger.py,sha256=StDWgtVoBOplRsvcH44Su-ikOWcafT29Kof-K-jiTxc,312
|
|
@@ -82,27 +82,27 @@ fiddler/tests/apis/test_alert_rule.py,sha256=3fmcxiGXOokMqFRWo5w4CL4m0k6rCnG17ZR
|
|
|
82
82
|
fiddler/tests/apis/test_baseline.py,sha256=R_vvbZdSenpYMSydurENJrE_uNWE3sJR9sHJksfU_Jk,7494
|
|
83
83
|
fiddler/tests/apis/test_custom_metric.py,sha256=QlNa80M9PxqaqzW-3nCtH5kKLRHYu2iqFDautBusbos,8208
|
|
84
84
|
fiddler/tests/apis/test_dataset.py,sha256=1Xc9Ng91fLMc2C6dpKfse42fQ0anza3ZOKOD1TqCvTQ,4636
|
|
85
|
-
fiddler/tests/apis/test_events.py,sha256=
|
|
85
|
+
fiddler/tests/apis/test_events.py,sha256=GzdfSqBN0WAGCIjWKmz1WPHsRafBiZnWtmd8feiNplw,5134
|
|
86
86
|
fiddler/tests/apis/test_files.py,sha256=-BisPck7bHMSW3N40ffdQJe027Qe3079tjTlU2OI9DU,4501
|
|
87
|
-
fiddler/tests/apis/test_generate_model.py,sha256
|
|
87
|
+
fiddler/tests/apis/test_generate_model.py,sha256=J8foDsZIUoHJyzLODQM57jw8OFVD81jc67RIubq-CVs,5089
|
|
88
88
|
fiddler/tests/apis/test_job.py,sha256=GlJXhxK60q9u5QJ6ToLrw4y1G3M0DzAAR2u-JDbk3D0,1891
|
|
89
89
|
fiddler/tests/apis/test_mixin.py,sha256=CmiyAch8Mzoi3Jru-8PWVxWlX-y30WMuoymSc9QXFwc,2453
|
|
90
|
-
fiddler/tests/apis/test_model.py,sha256=
|
|
90
|
+
fiddler/tests/apis/test_model.py,sha256=AL4BdP8vZjRLkh0XESFKjLzg5gVG-vxhnyE6adku6tA,18346
|
|
91
91
|
fiddler/tests/apis/test_model_artifact.py,sha256=oQVasstDc41EesAzN39wCTZi7KtpeQdVcGcLxERjmw0,5635
|
|
92
92
|
fiddler/tests/apis/test_model_deployment.py,sha256=AWGdbepFrU3qT17XAZC68n8LlXh7OKNqSwg3_ug9G5I,2441
|
|
93
93
|
fiddler/tests/apis/test_model_surrogate.py,sha256=h3OPgD1-_n6gTd0YH_hDcBBRjBfXv1xUsZxNWUU69KQ,4443
|
|
94
94
|
fiddler/tests/apis/test_project.py,sha256=IEYT3imD4dZJg-OT888G4jQJ_AxxHN5_VkCHcmwUnGA,6015
|
|
95
95
|
fiddler/tests/apis/test_segment.py,sha256=hQiXpZEKLJrnlRBLLMsP3jph8H8K7AEUw17sMK6BuF8,7804
|
|
96
96
|
fiddler/tests/apis/test_webhook.py,sha256=DTaEI9r2lossAaXb3b1CX4C8L3bYAzZmLIWH8VeauUs,6440
|
|
97
|
-
fiddler/tests/apis/test_xai.py,sha256=
|
|
97
|
+
fiddler/tests/apis/test_xai.py,sha256=mV-bcu6Tkw_wM4nCc9Uf6OXU38Hz0gX3eiVMCLGAsbQ,28590
|
|
98
98
|
fiddler/utils/__init__.py,sha256=ooAJR6_tVaF6UKZriz5ynZPj8IeCzaDIPKV23F-e4Fk,53
|
|
99
99
|
fiddler/utils/helpers.py,sha256=Xl_T_se9SOeMTmVJAOVzzmkekUsmpFMlAb2KvPCWfTw,2908
|
|
100
100
|
fiddler/utils/logger.py,sha256=FdJ3LkS9dbRjWsw5oJmNNsd7q0XRVEIvx5-TWys7L0k,669
|
|
101
101
|
fiddler/utils/model_generator.py,sha256=TwQMQDpy-0gcq6HyL68s37N-sk_nGPq33mmppK6i7hI,2203
|
|
102
102
|
fiddler/utils/validations.py,sha256=i8NtgrpCsitq6BPa5lygpKDh07oaOXu7PAlCIcMvqzY,408
|
|
103
103
|
fiddler/utils/version.py,sha256=iC8Ry7UFl9EJW_xU1WbzO_l4yK6V7eQ6U5exB3xT864,531
|
|
104
|
-
fiddler_client-3.
|
|
105
|
-
fiddler_client-3.
|
|
106
|
-
fiddler_client-3.
|
|
107
|
-
fiddler_client-3.
|
|
108
|
-
fiddler_client-3.
|
|
104
|
+
fiddler_client-3.1.0.dev1.dist-info/LICENSE.txt,sha256=w8-LUAb_VLBWSsCNmih0pAqLIicJfGu8OJXpDjkIg_o,559
|
|
105
|
+
fiddler_client-3.1.0.dev1.dist-info/METADATA,sha256=V8DvhQQmpuFptAUCsonzdA6mmbEVYzQmtU_qxMR0pUU,20679
|
|
106
|
+
fiddler_client-3.1.0.dev1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
107
|
+
fiddler_client-3.1.0.dev1.dist-info/top_level.txt,sha256=ndC6LqvKmrTTs8czRlBOYdYgSqZbHuMf0zHt_HWpzzk,8
|
|
108
|
+
fiddler_client-3.1.0.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|