fiddler-client 2.4.0.dev2__py3-none-any.whl → 2.5.0.dev2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. fiddler/__init__.py +2 -0
  2. fiddler/_version.py +1 -1
  3. fiddler/api/alert_mixin.py +51 -1
  4. fiddler/api/webhooks_mixin.py +6 -8
  5. fiddler/core_objects.py +76 -26
  6. fiddler/schema/alert.py +9 -2
  7. fiddler/schemas/custom_features.py +44 -1
  8. fiddler3/__init__.py +121 -38
  9. fiddler3/configs.py +3 -0
  10. fiddler3/connection.py +68 -1
  11. fiddler3/constants/alert_rule.py +30 -0
  12. fiddler3/constants/events.py +7 -0
  13. fiddler3/constants/model.py +9 -0
  14. fiddler3/entities/__init__.py +0 -7
  15. fiddler3/entities/alert_record.py +106 -0
  16. fiddler3/entities/alert_rule.py +323 -0
  17. fiddler3/entities/base.py +2 -16
  18. fiddler3/entities/baseline.py +21 -15
  19. fiddler3/entities/dataset.py +1 -1
  20. fiddler3/entities/events.py +136 -0
  21. fiddler3/entities/file.py +162 -0
  22. fiddler3/entities/model.py +141 -24
  23. fiddler3/entities/model_artifact.py +52 -224
  24. fiddler3/entities/model_deployment.py +12 -1
  25. fiddler3/entities/project.py +2 -2
  26. fiddler3/entities/{model_surrogate.py → surrogate.py} +27 -29
  27. fiddler3/entities/webhook.py +128 -0
  28. fiddler3/entities/xai.py +271 -5
  29. fiddler3/libs/http_client.py +1 -1
  30. fiddler3/libs/json_encoder.py +1 -1
  31. fiddler3/schemas/__init__.py +0 -9
  32. fiddler3/schemas/alert_record.py +27 -0
  33. fiddler3/schemas/alert_rule.py +39 -0
  34. fiddler3/schemas/custom_features.py +68 -12
  35. fiddler3/schemas/events.py +15 -0
  36. fiddler3/schemas/file.py +23 -0
  37. fiddler3/schemas/model.py +1 -6
  38. fiddler3/schemas/model_deployment.py +5 -4
  39. fiddler3/schemas/webhook.py +23 -0
  40. fiddler3/tests/apis/test_alert_record.py +99 -0
  41. fiddler3/tests/apis/test_alert_rule.py +393 -0
  42. fiddler3/tests/apis/test_events.py +169 -0
  43. fiddler3/tests/apis/test_files.py +164 -0
  44. fiddler3/tests/apis/test_model.py +60 -9
  45. fiddler3/tests/apis/test_model_artifact.py +21 -0
  46. fiddler3/tests/apis/test_project.py +3 -3
  47. fiddler3/tests/apis/test_webhook.py +257 -0
  48. fiddler3/tests/apis/test_xai.py +233 -1
  49. fiddler3/tests/conftest.py +2 -2
  50. fiddler3/tests/constants.py +5 -0
  51. fiddler3/tests/test_connection.py +24 -1
  52. fiddler3/tests/test_json_encoder.py +2 -2
  53. fiddler3/utils/helpers.py +20 -0
  54. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/METADATA +15 -5
  55. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/RECORD +59 -44
  56. tests/fiddler/test_alert.py +95 -5
  57. fiddler3/entities/helpers.py +0 -21
  58. fiddler3/schemas/model_artifact.py +0 -6
  59. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/LICENSE.txt +0 -0
  60. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/WHEEL +0 -0
  61. {fiddler_client-2.4.0.dev2.dist-info → fiddler_client-2.5.0.dev2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,99 @@
1
+ from datetime import datetime
2
+
3
+ import responses
4
+
5
+ from fiddler3.entities.alert_record import AlertRecord
6
+ from fiddler3.tests.constants import ALERT_RULE_ID, URL
7
+
8
+ ALERT_RECORD_LIST_RESPONSE = {
9
+ 'data': {
10
+ 'page_size': 100,
11
+ 'total': 2,
12
+ 'item_count': 2,
13
+ 'page_count': 1,
14
+ 'page_index': 1,
15
+ 'offset': 0,
16
+ 'items': [
17
+ {
18
+ 'id': 26410,
19
+ 'uuid': 'b520da6b-c01c-4375-bde6-2560cd972485',
20
+ 'alert_config_uuid': ALERT_RULE_ID,
21
+ 'alert_run_start_time': 1703255503133,
22
+ 'alert_time_bucket': 1703116800000,
23
+ 'baseline_time_bucket': None,
24
+ 'baseline_value': None,
25
+ 'is_alert': True,
26
+ 'severity': 'CRITICAL',
27
+ 'failure_reason': 'NA',
28
+ 'message': '',
29
+ 'alert_value': 37.0,
30
+ 'feature_name': 'estimatedsalary',
31
+ 'alert_record_main_version': 1,
32
+ 'alert_record_sub_version': 2,
33
+ 'created_at': '2023-12-22T14:31:43.499697+00:00',
34
+ 'updated_at': '2023-12-22T14:31:43.499697+00:00',
35
+ },
36
+ {
37
+ 'id': 26418,
38
+ 'uuid': '6e55540c-9053-4af8-b20e-1728b1436f35',
39
+ 'alert_config_uuid': ALERT_RULE_ID,
40
+ 'alert_run_start_time': 1703259102527,
41
+ 'alert_time_bucket': 1703116800000,
42
+ 'baseline_time_bucket': None,
43
+ 'baseline_value': None,
44
+ 'is_alert': True,
45
+ 'severity': 'CRITICAL',
46
+ 'failure_reason': 'NA',
47
+ 'message': '',
48
+ 'alert_value': 37.0,
49
+ 'feature_name': 'estimatedsalary',
50
+ 'alert_record_main_version': 1,
51
+ 'alert_record_sub_version': 3,
52
+ 'created_at': '2023-12-22T15:31:42.698623+00:00',
53
+ 'updated_at': '2023-12-22T15:31:42.698623+00:00',
54
+ },
55
+ ],
56
+ },
57
+ 'api_version': '2.0',
58
+ 'kind': 'PAGINATED',
59
+ }
60
+
61
+ ALERT_RECORD_EMPTY_LIST_RESPONSE = {
62
+ 'data': {
63
+ 'page_size': 100,
64
+ 'total': 0,
65
+ 'item_count': 0,
66
+ 'page_count': 1,
67
+ 'page_index': 1,
68
+ 'offset': 0,
69
+ 'items': [],
70
+ },
71
+ 'api_version': '2.0',
72
+ 'kind': 'PAGINATED',
73
+ }
74
+
75
+
76
+ @responses.activate
77
+ def test_alert_record_list_success() -> None:
78
+ responses.get(
79
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}/records',
80
+ json=ALERT_RECORD_LIST_RESPONSE,
81
+ )
82
+ alert_records = AlertRecord.list(
83
+ alert_rule_id=ALERT_RULE_ID,
84
+ start_time=datetime(2023, 12, 18),
85
+ end_time=datetime(2023, 12, 25),
86
+ )
87
+ for record in alert_records:
88
+ assert isinstance(record, AlertRecord)
89
+
90
+
91
+ @responses.activate
92
+ def test_alert_record_list_success() -> None:
93
+ responses.get(
94
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}/records',
95
+ json=ALERT_RECORD_EMPTY_LIST_RESPONSE,
96
+ )
97
+ alert_records = list(AlertRecord.list(alert_rule_id=ALERT_RULE_ID))
98
+
99
+ assert len(alert_records) == 0
@@ -0,0 +1,393 @@
1
+ import json
2
+ import logging
3
+ from http import HTTPStatus
4
+ from uuid import UUID
5
+
6
+ import pytest
7
+ import responses
8
+
9
+ from fiddler3.constants.alert_rule import AlertCondition, BinSize, CompareTo, Priority
10
+ from fiddler3.entities.alert_rule import AlertRule
11
+ from fiddler3.exceptions import NotFound
12
+ from fiddler3.schemas.alert_rule import NotificationConfig
13
+ from fiddler3.tests.apis.test_baseline import (
14
+ API_RESPONSE_200 as BASELINE_API_RESPONSE_200,
15
+ )
16
+ from fiddler3.tests.apis.test_model import API_RESPONSE_200 as MODEL_API_RESPONSE_200
17
+ from fiddler3.tests.constants import (
18
+ ALERT_RULE_ID,
19
+ BASELINE_ID,
20
+ BASELINE_NAME,
21
+ MODEL_ID,
22
+ MODEL_NAME,
23
+ PROJECT_ID,
24
+ PROJECT_NAME,
25
+ URL,
26
+ )
27
+ from fiddler3.utils.logger import set_logging
28
+
29
+ API_RESPONSE_200 = {
30
+ 'data': {
31
+ 'feature_names': ['age'],
32
+ 'id': 620,
33
+ 'created_at': '2023-11-28T08:04:12.651937+00:00',
34
+ 'organization_name': 'mainbuild',
35
+ 'compare_period': None,
36
+ 'time_bucket': 3600000,
37
+ 'alert_type': 'data_integrity',
38
+ 'metric_display_name': 'Missing Value Violation',
39
+ 'enable_notification': True,
40
+ 'last_updated': '2023-11-28T08:04:12.653346+00:00',
41
+ 'alert_type_display_name': 'Data Integrity',
42
+ 'critical_threshold': 0.0,
43
+ 'baseline_name': BASELINE_NAME,
44
+ 'baseline': {
45
+ 'id': BASELINE_ID,
46
+ 'name': BASELINE_NAME,
47
+ },
48
+ 'condition': 'greater',
49
+ 'uuid': ALERT_RULE_ID,
50
+ 'segment': None,
51
+ 'warning_threshold': None,
52
+ 'name': 'Age Missing 1 Hr',
53
+ 'model_name': MODEL_NAME,
54
+ 'model': {
55
+ 'id': MODEL_ID,
56
+ 'name': MODEL_NAME,
57
+ },
58
+ 'is_active': True,
59
+ 'created_by': 'admin@fiddler.ai',
60
+ 'bin_size': 'Hour',
61
+ 'category': None,
62
+ 'project_name': PROJECT_NAME,
63
+ 'project': {
64
+ 'id': PROJECT_ID,
65
+ 'name': PROJECT_NAME,
66
+ },
67
+ 'metric': 'null_violation_count',
68
+ 'compare_to': 'raw_value',
69
+ 'priority': 'HIGH',
70
+ 'notifications': {
71
+ 'emails': {'alert_config_uuid': ALERT_RULE_ID, 'email': 'admin@fiddler.ai'},
72
+ 'pagerduty': {
73
+ 'severity': '',
74
+ 'alert_config_uuid': ALERT_RULE_ID,
75
+ 'service': '',
76
+ },
77
+ 'webhooks': [{'uuid': 'e20bf4cc-d2cf-4540-baef-d96913b14f1b'}],
78
+ },
79
+ },
80
+ 'api_version': '2.0',
81
+ 'kind': 'NORMAL',
82
+ }
83
+
84
+ API_RESPONSE_404 = {
85
+ 'error': {
86
+ 'code': 404,
87
+ 'message': "AlertConfig({'uuid': 'ff9a897b-be5b-48e6-909f-13073a6d0fe8'}) not found",
88
+ 'errors': [
89
+ {
90
+ 'reason': 'ObjectNotFound',
91
+ 'message': "AlertConfig({'uuid': 'ff9a897b-be5b-48e6-909f-13073a6d0fe8'}) not found",
92
+ 'help': '',
93
+ }
94
+ ],
95
+ },
96
+ 'api_version': '2.0',
97
+ 'kind': 'ERROR',
98
+ }
99
+
100
+ LIST_API_RESPONSE = {
101
+ 'data': {
102
+ 'page_size': 100,
103
+ 'total': 2,
104
+ 'item_count': 2,
105
+ 'page_count': 1,
106
+ 'page_index': 1,
107
+ 'offset': 0,
108
+ 'items': [
109
+ API_RESPONSE_200['data'],
110
+ {
111
+ 'critical_threshold': 100.0,
112
+ 'created_by': 'admin@fiddler.ai',
113
+ 'metric_display_name': 'Traffic',
114
+ 'condition': 'lesser',
115
+ 'metric': 'traffic',
116
+ 'bin_size': 'Hour',
117
+ 'enable_notification': True,
118
+ 'is_active': True,
119
+ 'project_name': PROJECT_NAME,
120
+ 'project': {
121
+ 'id': PROJECT_ID,
122
+ 'name': PROJECT_NAME,
123
+ },
124
+ 'name': 'traffic-lt-1hr',
125
+ 'created_at': '2023-11-28T08:04:10.916498+00:00',
126
+ 'baseline_name': 'DEFAULT (bank_churn)',
127
+ 'baseline': {
128
+ 'id': BASELINE_ID,
129
+ 'name': BASELINE_NAME,
130
+ },
131
+ 'organization_name': 'mainbuild',
132
+ 'priority': 'HIGH',
133
+ 'category': None,
134
+ 'warning_threshold': 200.0,
135
+ 'segment': None,
136
+ 'alert_type': 'service_metrics',
137
+ 'time_bucket': 3600000,
138
+ 'alert_type_display_name': 'Traffic',
139
+ 'compare_period': 5,
140
+ 'compare_to': 'time_period',
141
+ 'uuid': '42eb6d51-dc5c-4e04-82aa-440b2d840704',
142
+ 'id': 618,
143
+ 'feature_names': None,
144
+ 'model_name': MODEL_NAME,
145
+ 'model': {
146
+ 'id': MODEL_ID,
147
+ 'name': MODEL_NAME,
148
+ },
149
+ 'last_updated': '2023-11-28T08:04:10.918587+00:00',
150
+ },
151
+ ],
152
+ }
153
+ }
154
+
155
+ LIST_API_RESPONSE_EMPTY = {
156
+ 'data': {
157
+ 'page_size': 100,
158
+ 'total': 2,
159
+ 'item_count': 2,
160
+ 'page_count': 1,
161
+ 'page_index': 1,
162
+ 'offset': 0,
163
+ 'items': [],
164
+ }
165
+ }
166
+
167
+
168
+ @responses.activate
169
+ def test_get_alert_rule_success() -> None:
170
+ responses.get(
171
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
172
+ json=API_RESPONSE_200,
173
+ )
174
+
175
+ alert_rule = AlertRule.get(id_=ALERT_RULE_ID)
176
+
177
+ assert isinstance(alert_rule, AlertRule)
178
+
179
+
180
+ @responses.activate
181
+ def test_get_alert_rule_not_found() -> None:
182
+ responses.get(
183
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
184
+ json=API_RESPONSE_404,
185
+ status=HTTPStatus.NOT_FOUND,
186
+ )
187
+
188
+ with pytest.raises(NotFound):
189
+ AlertRule.get(id_=ALERT_RULE_ID)
190
+
191
+
192
+ @responses.activate
193
+ def test_alert_rule_list_success() -> None:
194
+ responses.get(
195
+ url=f'{URL}/v2/alert-configs',
196
+ json=LIST_API_RESPONSE,
197
+ )
198
+ for rule in AlertRule.list():
199
+ assert isinstance(rule, AlertRule)
200
+
201
+
202
+ @responses.activate
203
+ def test_alert_rule_list_empty() -> None:
204
+
205
+ responses.get(
206
+ url=f'{URL}/v2/alert-configs',
207
+ json=LIST_API_RESPONSE_EMPTY,
208
+ )
209
+
210
+ assert len(list(AlertRule.list())) == 0
211
+
212
+
213
+ @responses.activate
214
+ def test_delete_alert_rule() -> None:
215
+ responses.get(
216
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
217
+ json=API_RESPONSE_200,
218
+ )
219
+ rule = AlertRule.get(id_=ALERT_RULE_ID)
220
+
221
+ responses.delete(
222
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
223
+ )
224
+
225
+ rule.delete()
226
+
227
+
228
+ @responses.activate
229
+ def test_delete_alert_rule_not_found() -> None:
230
+ responses.get(
231
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
232
+ json=API_RESPONSE_200,
233
+ )
234
+ rule = AlertRule.get(id_=ALERT_RULE_ID)
235
+
236
+ responses.delete(
237
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
238
+ json=API_RESPONSE_404,
239
+ status=HTTPStatus.NOT_FOUND,
240
+ )
241
+
242
+ with pytest.raises(NotFound):
243
+ rule.delete()
244
+
245
+
246
+ @responses.activate
247
+ def test_add_alert_rule_success() -> None:
248
+ responses.get(
249
+ url=f'{URL}/v3/models/{MODEL_ID}',
250
+ json=MODEL_API_RESPONSE_200,
251
+ )
252
+ responses.get(
253
+ url=f'{URL}/v3/baselines/{BASELINE_ID}',
254
+ json=BASELINE_API_RESPONSE_200,
255
+ )
256
+ responses.post(
257
+ url=f'{URL}/v2/alert-configs',
258
+ json=API_RESPONSE_200,
259
+ )
260
+ alert_rule = AlertRule(
261
+ name='alert_name',
262
+ model_id=MODEL_ID,
263
+ metric_id='drift',
264
+ priority=Priority.HIGH,
265
+ compare_to=CompareTo.RAW_VALUE,
266
+ condition=AlertCondition.GREATER,
267
+ bin_size=BinSize.HOUR,
268
+ critical_threshold=0.5,
269
+ warning_threshold=0.1,
270
+ columns=['gender', 'creditscore'],
271
+ ).create()
272
+
273
+ assert isinstance(alert_rule, AlertRule)
274
+ assert alert_rule.id == UUID(ALERT_RULE_ID)
275
+ assert alert_rule.model.id == UUID(MODEL_ID)
276
+ assert alert_rule.project_id == UUID(PROJECT_ID)
277
+
278
+
279
+ @responses.activate
280
+ def test_enable_notifications(caplog) -> None:
281
+ responses.get(
282
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
283
+ json=API_RESPONSE_200,
284
+ )
285
+
286
+ alert_rule = AlertRule.get(id_=ALERT_RULE_ID)
287
+
288
+ resp = responses.patch(
289
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
290
+ json=API_RESPONSE_200,
291
+ )
292
+ set_logging(logging.INFO)
293
+
294
+ alert_rule.enable_notifications()
295
+ assert json.loads(resp.calls[0].request.body) == {'enable_notification': True}
296
+ assert (
297
+ f'Notifications have been enabled for alert rule with id: {ALERT_RULE_ID}'
298
+ == caplog.messages[0]
299
+ )
300
+
301
+
302
+ @responses.activate
303
+ def test_disable_notifications(caplog) -> None:
304
+ responses.get(
305
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
306
+ json=API_RESPONSE_200,
307
+ )
308
+
309
+ alert_rule = AlertRule.get(id_=ALERT_RULE_ID)
310
+
311
+ API_RESPONSE_200['enable_notification'] = False
312
+
313
+ resp = responses.patch(
314
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
315
+ json=API_RESPONSE_200,
316
+ )
317
+
318
+ set_logging(logging.INFO)
319
+ alert_rule.disable_notifications()
320
+ assert json.loads(resp.calls[0].request.body) == {'enable_notification': False}
321
+ assert (
322
+ f'Notifications have been disabled for alert rule with id: {ALERT_RULE_ID}'
323
+ == caplog.messages[0]
324
+ )
325
+
326
+
327
+ @responses.activate
328
+ def test_set_notifications() -> None:
329
+ responses.get(
330
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
331
+ json=API_RESPONSE_200,
332
+ )
333
+
334
+ alert_rule = AlertRule.get(id_=ALERT_RULE_ID)
335
+ API_RESPONSE_200['data']['notifications'] = {
336
+ 'webhooks': [
337
+ {'uuid': 'e20bf4cc-d2cf-4540-baef-d96913b14f1b'},
338
+ {'uuid': '6e796fda-0111-4a72-82cd-f0f219e903e1'},
339
+ ],
340
+ 'emails': {
341
+ 'alert_config_uuid': ALERT_RULE_ID,
342
+ 'email': 'nikhil@fiddler.ai, admin@fiddler.ai',
343
+ },
344
+ 'pagerduty': {
345
+ 'service': '',
346
+ 'alert_config_uuid': ALERT_RULE_ID,
347
+ 'severity': '',
348
+ },
349
+ }
350
+ responses.patch(
351
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
352
+ json=API_RESPONSE_200,
353
+ )
354
+ notifications = alert_rule.set_notification_config(
355
+ emails=['nikhil@fiddler.ai', 'admin@fiddler.ai'],
356
+ webhooks=[
357
+ 'e20bf4cc-d2cf-4540-baef-d96913b14f1b',
358
+ '6e796fda-0111-4a72-82cd-f0f219e903e1',
359
+ ],
360
+ )
361
+ assert notifications == NotificationConfig(
362
+ **{
363
+ 'emails': ['nikhil@fiddler.ai', 'admin@fiddler.ai'],
364
+ 'webhooks': [
365
+ 'e20bf4cc-d2cf-4540-baef-d96913b14f1b',
366
+ '6e796fda-0111-4a72-82cd-f0f219e903e1',
367
+ ],
368
+ }
369
+ )
370
+
371
+
372
+ @responses.activate
373
+ def test_get_notifications() -> None:
374
+ responses.get(
375
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
376
+ json=API_RESPONSE_200,
377
+ )
378
+
379
+ alert_rule = AlertRule.get(id_=ALERT_RULE_ID)
380
+ responses.get(
381
+ url=f'{URL}/v2/alert-configs/{ALERT_RULE_ID}',
382
+ json=API_RESPONSE_200,
383
+ )
384
+ notifications = alert_rule.get_notification_config()
385
+ assert notifications == NotificationConfig(
386
+ **{
387
+ 'emails': ['nikhil@fiddler.ai', 'admin@fiddler.ai'],
388
+ 'webhooks': [
389
+ 'e20bf4cc-d2cf-4540-baef-d96913b14f1b',
390
+ '6e796fda-0111-4a72-82cd-f0f219e903e1',
391
+ ],
392
+ }
393
+ )
@@ -0,0 +1,169 @@
1
+ from pathlib import Path
2
+ from uuid import uuid4
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import responses
7
+
8
+ from fiddler3.constants.dataset import EnvType
9
+ from fiddler3.entities.job import Job
10
+ from fiddler3.entities.model import Model
11
+ from fiddler3.tests.apis.test_files import SINGLE_UPLOAD_200_RESPONSE
12
+ from fiddler3.tests.apis.test_model import API_RESPONSE_200 as MODEL_API_RESPONSE_200
13
+ from fiddler3.tests.constants import JOB_ID, MODEL_ID, URL
14
+
15
+ FILE_PUBLISH_202_API_RESPONSE = {
16
+ 'data': {
17
+ 'source_type': 'FILE',
18
+ 'job': {'id': JOB_ID, 'name': 'Upload dataset'},
19
+ },
20
+ 'api_version': '3.0',
21
+ 'kind': 'NORMAL',
22
+ }
23
+
24
+ PUBLISH_JOB_RESPONSE = {
25
+ 'data': {
26
+ 'id': 'e5784edf-2361-43e1-b67f-3dec90039d9b',
27
+ 'name': 'Publish events',
28
+ 'info': {
29
+ 'env_name': 'production',
30
+ 'env_type': 'PRODUCTION',
31
+ 'model_name': 'bank_churn',
32
+ 'model_uuid': 'd30bc065-a2ca-461e-bdcc-56d47c85d2a3',
33
+ 'project_name': 'test_project_5',
34
+ 'resource_name': 'bank_churn',
35
+ 'resource_type': 'EVENT',
36
+ '__tracker_info': {
37
+ 'progress': 0,
38
+ 'error_reason': None,
39
+ 'error_message': None,
40
+ },
41
+ },
42
+ 'status': 'PENDING',
43
+ 'progress': 0,
44
+ 'error_message': None,
45
+ 'error_reason': None,
46
+ },
47
+ 'api_version': '3.0',
48
+ 'kind': 'NORMAL',
49
+ }
50
+
51
+ df = pd.DataFrame(np.random.randint(0, 100, size=(10, 4)), columns=list('ABCD'))
52
+
53
+ STREAM_PUBLISH_202_API_RESPONSE = {
54
+ 'data': {
55
+ 'source_type': 'EVENTS',
56
+ 'fiddler_ids': [str(uuid4()) for i in range(df.shape[0])],
57
+ },
58
+ 'api_version': '3.0',
59
+ 'kind': 'NORMAL',
60
+ }
61
+
62
+ BASE_TEST_DIR = Path(__file__).resolve().parent.parent
63
+ FILE_PATH = BASE_TEST_DIR / 'artifact_test_dir' / 'model.yaml'
64
+
65
+
66
+ @responses.activate
67
+ def test_publish_file() -> None:
68
+ responses.get(
69
+ url=f'{URL}/v3/models/{MODEL_ID}',
70
+ json=MODEL_API_RESPONSE_200,
71
+ )
72
+ model = Model.get(id_=MODEL_ID)
73
+
74
+ responses.post(
75
+ url=f'{URL}/v3/files/upload',
76
+ json=SINGLE_UPLOAD_200_RESPONSE,
77
+ )
78
+ responses.get(
79
+ url=f'{URL}/v3/jobs/{JOB_ID}',
80
+ json=PUBLISH_JOB_RESPONSE,
81
+ )
82
+
83
+ responses.post(
84
+ url=f'{URL}/v3/events',
85
+ json=FILE_PUBLISH_202_API_RESPONSE,
86
+ )
87
+ publish_response = model.publish(
88
+ source=FILE_PATH, environment=EnvType.PRE_PRODUCTION, dataset_name='dataset_1'
89
+ )
90
+
91
+ assert isinstance(publish_response, Job)
92
+
93
+ FILE_PUBLISH_202_API_RESPONSE['data']['job']['name'] = 'Publish events'
94
+
95
+ publish_response = model.publish(
96
+ source=FILE_PATH, environment=EnvType.PRODUCTION, dataset_name='dataset_2'
97
+ )
98
+
99
+ assert isinstance(publish_response, Job)
100
+
101
+
102
+ @responses.activate
103
+ def test_publish_stream() -> None:
104
+ responses.get(
105
+ url=f'{URL}/v3/models/{MODEL_ID}',
106
+ json=MODEL_API_RESPONSE_200,
107
+ )
108
+ model = Model.get(id_=MODEL_ID)
109
+
110
+ responses.post(
111
+ url=f'{URL}/v3/events',
112
+ json=STREAM_PUBLISH_202_API_RESPONSE,
113
+ )
114
+ publish_response = model.publish(
115
+ source=df,
116
+ environment=EnvType.PRODUCTION,
117
+ )
118
+
119
+ assert isinstance(publish_response, list)
120
+ assert len(publish_response) == df.shape[0]
121
+
122
+ publish_response = model.publish(
123
+ source=df.to_dict('records'),
124
+ environment=EnvType.PRODUCTION,
125
+ )
126
+
127
+ assert isinstance(publish_response, list)
128
+ assert len(publish_response) == df.shape[0]
129
+
130
+
131
+ @responses.activate
132
+ def test_publish_update() -> None:
133
+ responses.get(
134
+ url=f'{URL}/v3/models/{MODEL_ID}',
135
+ json=MODEL_API_RESPONSE_200,
136
+ )
137
+ model = Model.get(id_=MODEL_ID)
138
+
139
+ responses.patch(
140
+ url=f'{URL}/v3/events',
141
+ json=STREAM_PUBLISH_202_API_RESPONSE,
142
+ )
143
+ publish_response = model.publish(
144
+ source=df, environment=EnvType.PRODUCTION, update=True
145
+ )
146
+ assert isinstance(publish_response, list)
147
+ assert len(publish_response) == df.shape[0]
148
+
149
+ responses.post(
150
+ url=f'{URL}/v3/files/upload',
151
+ json=SINGLE_UPLOAD_200_RESPONSE,
152
+ )
153
+ responses.get(
154
+ url=f'{URL}/v3/jobs/{JOB_ID}',
155
+ json=PUBLISH_JOB_RESPONSE,
156
+ )
157
+
158
+ responses.patch(
159
+ url=f'{URL}/v3/events',
160
+ json=FILE_PUBLISH_202_API_RESPONSE,
161
+ )
162
+ publish_response = model.publish(
163
+ source=FILE_PATH,
164
+ environment=EnvType.PRODUCTION,
165
+ dataset_name='dataset_1',
166
+ update=True,
167
+ )
168
+
169
+ assert isinstance(publish_response, Job)