fiddler-client 3.8.2__tar.gz → 3.9.0__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 (121) hide show
  1. {fiddler-client-3.8.2/fiddler_client.egg-info → fiddler-client-3.9.0}/PKG-INFO +1 -1
  2. fiddler-client-3.9.0/fiddler/VERSION +1 -0
  3. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/webhook.py +2 -2
  4. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/webhook.py +4 -2
  5. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_webhook.py +19 -15
  6. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/constants.py +2 -1
  7. fiddler-client-3.9.0/fiddler/tests/test_utils.py +431 -0
  8. fiddler-client-3.9.0/fiddler/utils/column_generator.py +68 -0
  9. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/utils/model_generator.py +1 -0
  10. {fiddler-client-3.8.2 → fiddler-client-3.9.0/fiddler_client.egg-info}/PKG-INFO +1 -1
  11. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler_client.egg-info/SOURCES.txt +1 -0
  12. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler_client.egg-info/requires.txt +1 -1
  13. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/setup.py +1 -1
  14. fiddler-client-3.8.2/fiddler/VERSION +0 -1
  15. fiddler-client-3.8.2/fiddler/tests/test_utils.py +0 -99
  16. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/LICENSE.txt +0 -0
  17. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/MANIFEST.in +0 -0
  18. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/PUBLIC.md +0 -0
  19. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/README.md +0 -0
  20. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/__init__.py +0 -0
  21. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/configs.py +0 -0
  22. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/connection.py +0 -0
  23. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/__init__.py +0 -0
  24. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/alert_rule.py +0 -0
  25. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/baseline.py +0 -0
  26. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/common.py +0 -0
  27. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/dataset.py +0 -0
  28. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/events.py +0 -0
  29. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/job.py +0 -0
  30. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/model.py +0 -0
  31. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/model_deployment.py +0 -0
  32. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/constants/xai.py +0 -0
  33. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/decorators.py +0 -0
  34. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/__init__.py +0 -0
  35. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/alert_record.py +0 -0
  36. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/alert_rule.py +0 -0
  37. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/base.py +0 -0
  38. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/baseline.py +0 -0
  39. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/custom_expression.py +0 -0
  40. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/dataset.py +0 -0
  41. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/events.py +0 -0
  42. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/file.py +0 -0
  43. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/job.py +0 -0
  44. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/model.py +0 -0
  45. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/model_artifact.py +0 -0
  46. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/model_deployment.py +0 -0
  47. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/organization.py +0 -0
  48. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/project.py +0 -0
  49. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/surrogate.py +0 -0
  50. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/user.py +0 -0
  51. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/entities/xai.py +0 -0
  52. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/exceptions.py +0 -0
  53. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/libs/__init__.py +0 -0
  54. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/libs/aws.py +0 -0
  55. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/libs/http_client.py +0 -0
  56. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/libs/json_encoder.py +0 -0
  57. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/libs/semver.py +0 -0
  58. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/packtools/__init__.py +0 -0
  59. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/packtools/gem.py +0 -0
  60. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/packtools/keras_ig_helpers.py +0 -0
  61. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/packtools/project_attributions_helpers.py +0 -0
  62. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/packtools/template_model.py +0 -0
  63. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/__init__.py +0 -0
  64. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/alert_record.py +0 -0
  65. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/alert_rule.py +0 -0
  66. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/base.py +0 -0
  67. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/baseline.py +0 -0
  68. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/custom_expression.py +0 -0
  69. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/custom_features.py +0 -0
  70. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/dataset.py +0 -0
  71. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/events.py +0 -0
  72. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/file.py +0 -0
  73. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/filter_query.py +0 -0
  74. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/job.py +0 -0
  75. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/model.py +0 -0
  76. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/model_deployment.py +0 -0
  77. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/model_schema.py +0 -0
  78. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/model_spec.py +0 -0
  79. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/model_task_params.py +0 -0
  80. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/organization.py +0 -0
  81. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/project.py +0 -0
  82. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/response.py +0 -0
  83. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/server_info.py +0 -0
  84. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/user.py +0 -0
  85. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/xai.py +0 -0
  86. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/schemas/xai_params.py +0 -0
  87. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/__init__.py +0 -0
  88. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/__init__.py +0 -0
  89. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_alert_record.py +0 -0
  90. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_alert_rule.py +0 -0
  91. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_baseline.py +0 -0
  92. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_custom_metric.py +0 -0
  93. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_dataset.py +0 -0
  94. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_events.py +0 -0
  95. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_files.py +0 -0
  96. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_generate_model.py +0 -0
  97. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_job.py +0 -0
  98. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_mixin.py +0 -0
  99. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_model.py +0 -0
  100. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_model_artifact.py +0 -0
  101. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_model_deployment.py +0 -0
  102. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_model_surrogate.py +0 -0
  103. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_project.py +0 -0
  104. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_segment.py +0 -0
  105. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/apis/test_xai.py +0 -0
  106. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/conftest.py +0 -0
  107. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/test_connection.py +0 -0
  108. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/test_json_encoder.py +0 -0
  109. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/test_logger.py +0 -0
  110. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/test_sagemaker_auth.py +0 -0
  111. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/tests/utils.py +0 -0
  112. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/utils/__init__.py +0 -0
  113. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/utils/decorators.py +0 -0
  114. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/utils/helpers.py +0 -0
  115. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/utils/logger.py +0 -0
  116. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/utils/validations.py +0 -0
  117. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/utils/version.py +0 -0
  118. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler/version.py +0 -0
  119. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler_client.egg-info/dependency_links.txt +0 -0
  120. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/fiddler_client.egg-info/top_level.txt +0 -0
  121. {fiddler-client-3.8.2 → fiddler-client-3.9.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fiddler-client
3
- Version: 3.8.2
3
+ Version: 3.9.0
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.9.0
@@ -20,7 +20,7 @@ class Webhook(BaseEntity):
20
20
  """
21
21
  self.name = name
22
22
  self.url = url
23
- # provider is 'SLACK' or 'OTHER' as of Aug 2023.
23
+ # provider is 'SLACK', 'MS_TEAMS' as of May 2025.
24
24
  self.provider = provider
25
25
 
26
26
  self.id: UUID | None = None
@@ -113,7 +113,7 @@ class Webhook(BaseEntity):
113
113
 
114
114
  :params name: name of webhook
115
115
  :params url: webhook url
116
- :params provider: Either 'SLACK' or 'OTHER'
116
+ :params provider: Either 'SLACK' or 'MS_TEAMS'
117
117
 
118
118
  :returns: Created `Webhook` object.
119
119
  """
@@ -2,15 +2,17 @@ import enum
2
2
  from datetime import datetime
3
3
  from uuid import UUID
4
4
 
5
- from pydantic.fields import Field
5
+ from pydantic.v1 import Field
6
6
 
7
7
  from fiddler.schemas.base import BaseModel
8
8
 
9
9
 
10
10
  @enum.unique
11
11
  class WebhookProvider(str, enum.Enum):
12
- # provider is 'SLACK' or 'OTHER' as of Aug 2023.
12
+ # provider is 'SLACK' or 'MS_TEAMS' as of May 2025.
13
+ # keeping OTHER in enum for existing webhooks for backwards compatibility
13
14
  SLACK = 'SLACK'
15
+ MS_TEAMS = 'MS_TEAMS'
14
16
  OTHER = 'OTHER'
15
17
 
16
18
 
@@ -13,6 +13,7 @@ from fiddler.tests.constants import (
13
13
  ORG_NAME,
14
14
  URL,
15
15
  WEBHOOK_ID,
16
+ WEBHOOK_UUID,
16
17
  WEBHOOK_NAME,
17
18
  WEBHOOK_PROVIDER,
18
19
  WEBHOOK_URL,
@@ -21,6 +22,7 @@ from fiddler.tests.constants import (
21
22
  API_RESPONSE_200 = {
22
23
  'data': {
23
24
  'id': WEBHOOK_ID,
25
+ 'uuid': WEBHOOK_UUID,
24
26
  'name': WEBHOOK_NAME,
25
27
  'url': WEBHOOK_URL,
26
28
  'provider': WEBHOOK_PROVIDER,
@@ -62,6 +64,7 @@ API_RESPONSE_FROM_NAME = {
62
64
  # and `id` is of type UUID. Should we allow for additional
63
65
  # properties?
64
66
  'id': WEBHOOK_ID,
67
+ 'uuid': WEBHOOK_UUID,
65
68
  'name': WEBHOOK_NAME,
66
69
  'url': WEBHOOK_URL,
67
70
  'provider': WEBHOOK_PROVIDER,
@@ -85,6 +88,7 @@ LIST_API_RESPONSE = {
85
88
  API_RESPONSE_200['data'],
86
89
  {
87
90
  'id': WEBHOOK_ID,
91
+ 'uuid': WEBHOOK_UUID,
88
92
  'name': 'test_webhook_config_name2',
89
93
  'url': WEBHOOK_URL,
90
94
  'provider': WEBHOOK_PROVIDER,
@@ -128,10 +132,10 @@ API_RESPONSE_409 = {
128
132
  @responses.activate
129
133
  def test_get_webhook_success() -> None:
130
134
  responses.get(
131
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
135
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
132
136
  json=API_RESPONSE_200,
133
137
  )
134
- webhook = Webhook.get(id_=WEBHOOK_ID)
138
+ webhook = Webhook.get(id_=WEBHOOK_UUID)
135
139
  assert isinstance(webhook, Webhook)
136
140
 
137
141
 
@@ -213,7 +217,7 @@ def test_add_webhook_success() -> None:
213
217
  name=WEBHOOK_NAME, url=WEBHOOK_URL, provider=WEBHOOK_PROVIDER
214
218
  ).create()
215
219
  assert isinstance(webhook, Webhook)
216
- assert webhook.id == UUID(WEBHOOK_ID)
220
+ assert webhook.id == UUID(WEBHOOK_UUID)
217
221
  assert webhook.name == WEBHOOK_NAME
218
222
  assert webhook.created_at == datetime.fromisoformat(
219
223
  API_RESPONSE_200['data']['created_at']
@@ -246,14 +250,14 @@ def test_add_webhook_conflict() -> None:
246
250
  @responses.activate
247
251
  def test_update_webhook_success(mocker) -> None:
248
252
  responses.get(
249
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
253
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
250
254
  json=API_RESPONSE_200,
251
255
  )
252
- webhook = Webhook.get(id_=WEBHOOK_ID)
256
+ webhook = Webhook.get(id_=WEBHOOK_UUID)
253
257
 
254
258
  mocker.patch.dict(API_RESPONSE_200['data'], {'name': 'test1'})
255
259
  responses.patch(
256
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
260
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
257
261
  json=API_RESPONSE_200,
258
262
  )
259
263
  webhook.name = 'test1'
@@ -266,31 +270,31 @@ def test_update_webhook_success(mocker) -> None:
266
270
  @responses.activate
267
271
  def test_update_webhook_not_found() -> None:
268
272
  responses.get(
269
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
273
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
270
274
  json=API_RESPONSE_200,
271
275
  )
272
276
 
273
277
  responses.patch(
274
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
278
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
275
279
  json=API_RESPONSE_404,
276
280
  status=HTTPStatus.NOT_FOUND,
277
281
  )
278
282
 
279
283
  with pytest.raises(NotFound):
280
- webhook = Webhook.get(id_=WEBHOOK_ID)
284
+ webhook = Webhook.get(id_=WEBHOOK_UUID)
281
285
  webhook.update()
282
286
 
283
287
 
284
288
  @responses.activate
285
289
  def test_delete_webhook() -> None:
286
290
  responses.get(
287
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
291
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
288
292
  json=API_RESPONSE_200,
289
293
  )
290
- webhook = Webhook.get(id_=WEBHOOK_ID)
294
+ webhook = Webhook.get(id_=WEBHOOK_UUID)
291
295
 
292
296
  responses.delete(
293
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
297
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
294
298
  json={},
295
299
  )
296
300
  response = webhook.delete()
@@ -300,13 +304,13 @@ def test_delete_webhook() -> None:
300
304
  @responses.activate
301
305
  def test_delete_webhook_not_found() -> None:
302
306
  responses.get(
303
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
307
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
304
308
  json=API_RESPONSE_200,
305
309
  )
306
- webhook = Webhook.get(id_=WEBHOOK_ID)
310
+ webhook = Webhook.get(id_=WEBHOOK_UUID)
307
311
 
308
312
  responses.delete(
309
- url=f'{URL}/v2/webhooks/{WEBHOOK_ID}',
313
+ url=f'{URL}/v2/webhooks/{WEBHOOK_UUID}',
310
314
  json=API_RESPONSE_404,
311
315
  status=HTTPStatus.NOT_FOUND,
312
316
  )
@@ -30,7 +30,8 @@ TEST_WEBHOOKS = [
30
30
  ]
31
31
  BASELINE_NAME = 'test_baseline'
32
32
  MODEL_DEPLOYMENT_ID = 'fc07562a-1a27-4f66-9154-732d3b14b98a'
33
- WEBHOOK_ID = 'a5b654eb-15c8-43c8-9d50-9ba6eea9a0ff'
33
+ WEBHOOK_UUID: str = 'a5b654eb-15c8-43c8-9d50-9ba6eea9a0ff'
34
+ WEBHOOK_ID: int = 1
34
35
  WEBHOOK_NAME = 'test_webhook_config_name'
35
36
  WEBHOOK_URL = 'https://www.slack.com'
36
37
  WEBHOOK_PROVIDER = 'SLACK'
@@ -0,0 +1,431 @@
1
+ import os
2
+ import tempfile
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import pandas as pd
6
+ import pytest
7
+
8
+ from fiddler.constants.model import DataType
9
+ from fiddler.schemas.server_info import Version
10
+ from fiddler.utils.helpers import group_by, try_series_retype
11
+ from fiddler.utils.column_generator import create_columns_from_df
12
+ from fiddler.utils.validations import validate_artifact_dir
13
+ from fiddler.utils.version import match_semver
14
+
15
+ MODEL_DATA = [{'Unnamed: 0': 43660,
16
+ 'CreditScore': 681,
17
+ 'Geography': 'France',
18
+ 'Gender': 'Female',
19
+ 'Age': 45,
20
+ 'Tenure': 4,
21
+ 'Balance': 150461.78248572032,
22
+ 'NumOfProducts': 1,
23
+ 'HasCrCard': True,
24
+ 'IsActiveMember': True,
25
+ 'EstimatedSalary': 121476.50483532489,
26
+ 'Churned': 'Not Churned',
27
+ 'probability_churned': 0.2070909278091097},
28
+ {'Unnamed: 0': 87278,
29
+ 'CreditScore': 604,
30
+ 'Geography': 'Spain',
31
+ 'Gender': 'Male',
32
+ 'Age': 25,
33
+ 'Tenure': 8,
34
+ 'Balance': 870.563948633173,
35
+ 'NumOfProducts': 2,
36
+ 'HasCrCard': False,
37
+ 'IsActiveMember': True,
38
+ 'EstimatedSalary': 39030.86584360056,
39
+ 'Churned': 'Not Churned',
40
+ 'probability_churned': 0.0619905773329336},
41
+ {'Unnamed: 0': 14317,
42
+ 'CreditScore': 648,
43
+ 'Geography': 'France',
44
+ 'Gender': 'Female',
45
+ 'Age': 50,
46
+ 'Tenure': 6,
47
+ 'Balance': 127661.4013780268,
48
+ 'NumOfProducts': 1,
49
+ 'HasCrCard': True,
50
+ 'IsActiveMember': True,
51
+ 'EstimatedSalary': -8216.031548157727,
52
+ 'Churned': 'Churned',
53
+ 'probability_churned': 0.1026814066436183},
54
+ {'Unnamed: 0': 81932,
55
+ 'CreditScore': 670,
56
+ 'Geography': 'France',
57
+ 'Gender': 'Male',
58
+ 'Age': 49,
59
+ 'Tenure': 7,
60
+ 'Balance': 95199.48061342211,
61
+ 'NumOfProducts': 3,
62
+ 'HasCrCard': False,
63
+ 'IsActiveMember': True,
64
+ 'EstimatedSalary': 47114.97249302002,
65
+ 'Churned': 'Not Churned',
66
+ 'probability_churned': 0.2134035806234498},
67
+ {'Unnamed: 0': 95321,
68
+ 'CreditScore': 616,
69
+ 'Geography': 'Germany',
70
+ 'Gender': 'Female',
71
+ 'Age': 35,
72
+ 'Tenure': 1,
73
+ 'Balance': 88151.28675933508,
74
+ 'NumOfProducts': 1,
75
+ 'HasCrCard': True,
76
+ 'IsActiveMember': False,
77
+ 'EstimatedSalary': 107882.06957422369,
78
+ 'Churned': 'Churned',
79
+ 'probability_churned': 0.8824657828379177},
80
+ {'Unnamed: 0': 5405,
81
+ 'CreditScore': 536,
82
+ 'Geography': 'France',
83
+ 'Gender': 'Female',
84
+ 'Age': 41,
85
+ 'Tenure': 4,
86
+ 'Balance': 167680.40434914205,
87
+ 'NumOfProducts': 1,
88
+ 'HasCrCard': True,
89
+ 'IsActiveMember': True,
90
+ 'EstimatedSalary': 133588.33926550962,
91
+ 'Churned': 'Not Churned',
92
+ 'probability_churned': 0.1189636863349831},
93
+ {'Unnamed: 0': 33188,
94
+ 'CreditScore': 666,
95
+ 'Geography': 'Spain',
96
+ 'Gender': 'Male',
97
+ 'Age': 34,
98
+ 'Tenure': 2,
99
+ 'Balance': 120370.38776657668,
100
+ 'NumOfProducts': 2,
101
+ 'HasCrCard': False,
102
+ 'IsActiveMember': True,
103
+ 'EstimatedSalary': 60070.356958972116,
104
+ 'Churned': 'Not Churned',
105
+ 'probability_churned': 0.1244325159224678},
106
+ {'Unnamed: 0': 63421,
107
+ 'CreditScore': 613,
108
+ 'Geography': 'France',
109
+ 'Gender': 'Male',
110
+ 'Age': 35,
111
+ 'Tenure': 2,
112
+ 'Balance': 1632.9882268964022,
113
+ 'NumOfProducts': 2,
114
+ 'HasCrCard': True,
115
+ 'IsActiveMember': True,
116
+ 'EstimatedSalary': 77206.59807746019,
117
+ 'Churned': 'Not Churned',
118
+ 'probability_churned': 0.1037151191218035},
119
+ {'Unnamed: 0': 72897,
120
+ 'CreditScore': 591,
121
+ 'Geography': 'Germany',
122
+ 'Gender': 'Male',
123
+ 'Age': 85,
124
+ 'Tenure': 4,
125
+ 'Balance': 115817.11190439922,
126
+ 'NumOfProducts': 1,
127
+ 'HasCrCard': False,
128
+ 'IsActiveMember': False,
129
+ 'EstimatedSalary': 23310.677701467852,
130
+ 'Churned': 'Not Churned',
131
+ 'probability_churned': 0.2658220429852892},
132
+ {'Unnamed: 0': 9507,
133
+ 'CreditScore': 488,
134
+ 'Geography': 'Spain',
135
+ 'Gender': 'Female',
136
+ 'Age': 40,
137
+ 'Tenure': 8,
138
+ 'Balance': 101678.5269411524,
139
+ 'NumOfProducts': 1,
140
+ 'HasCrCard': True,
141
+ 'IsActiveMember': False,
142
+ 'EstimatedSalary': 9013.186551877934,
143
+ 'Churned': 'Not Churned',
144
+ 'probability_churned': 0.1167422545893403}
145
+ ]
146
+
147
+ def test_match_semvar_version() -> None:
148
+ assert match_semver(None, '>=22.9.0') is False
149
+ assert match_semver(Version.parse('22.9.0'), '>=22.10.0') is False
150
+ assert match_semver(Version.parse('22.10.0'), '>=22.10.0') is True
151
+ assert match_semver(Version.parse('22.10.0'), '>22.10.0') is False
152
+ assert match_semver(Version.parse('22.11.0'), '>=22.10.0') is True
153
+ assert match_semver(Version.parse('22.11.0'), '>22.10.0') is True
154
+ assert match_semver(Version.parse('22.10.0'), '<=22.10.0') is True
155
+ assert match_semver(Version.parse('22.10.0'), '<22.10.0') is False
156
+ assert match_semver(Version.parse('22.9.0'), '<22.10.0') is True
157
+ assert match_semver(Version.parse('22.11.0-RC1'), '>=22.11.0') is True
158
+
159
+
160
+ def test_validate_artifact_dir(tmp_path) -> None:
161
+ artifact_dir = os.path.join(Path(__file__).resolve().parent, 'artifact_test_dir')
162
+ assert validate_artifact_dir(Path(artifact_dir)) is None
163
+ # Test for artifact_dir not valid directory
164
+ with pytest.raises(ValueError):
165
+ validate_artifact_dir(Path('test'))
166
+ # Test for package.py file not found
167
+ mock_dir = tmp_path / 'test'
168
+ mock_dir.mkdir()
169
+ with pytest.raises(ValueError):
170
+ validate_artifact_dir(Path(mock_dir))
171
+
172
+
173
+ def test_group_by_helper() -> None:
174
+ df = pd.DataFrame(
175
+ [
176
+ {'col1': 1, 'col2': 'foo'},
177
+ {'col1': 2, 'col2': 'bar'},
178
+ {'col1': 3, 'col2': 'baz'},
179
+ {'col1': 3, 'col2': 'foo'},
180
+ ]
181
+ )
182
+
183
+ # with output_path
184
+ with tempfile.TemporaryDirectory() as temp_dir:
185
+ file_path = Path(temp_dir) / 'data.csv'
186
+
187
+ assert file_path.exists() is False
188
+ group_by(df=df, group_by_col='col2', output_path=file_path)
189
+ assert file_path.exists() is True
190
+
191
+ # without output path
192
+ grouped_df = group_by(df=df, group_by_col='col2')
193
+ assert grouped_df.equals(
194
+ pd.DataFrame(
195
+ [
196
+ {'col2': 'foo', 'col1': [1, 3]},
197
+ {'col2': 'bar', 'col1': [2]},
198
+ {'col2': 'baz', 'col1': [3]},
199
+ ]
200
+ )
201
+ )
202
+
203
+
204
+ def test_try_series_retype() -> None:
205
+ series = pd.Series([1, 2, 3], dtype='float')
206
+ series = try_series_retype(series, 'int')
207
+ assert series.dtype == 'int'
208
+
209
+ series = pd.Series([1, 2, None])
210
+ series = try_series_retype(series, 'int')
211
+ assert series.dtype == 'float'
212
+
213
+
214
+ def test_try_series_retype_str_or_unkown() -> None:
215
+ series = pd.Series(['HIGH', 'MEDIUM', '', None], dtype='str')
216
+ series = try_series_retype(series, 'str')
217
+ assert series.dtype == 'object'
218
+
219
+
220
+ def test_try_series_retype_timestamp() -> None:
221
+ series = pd.Series(
222
+ ['2023-11-12 09:15:32.23', '2023-12-11 09:15:32.45'], dtype='str'
223
+ )
224
+ series = try_series_retype(series, 'timestamp')
225
+ assert series.dtype == 'datetime64[ns]'
226
+
227
+
228
+ def test_try_series_retype_timestamp_error() -> None:
229
+ series = pd.Series(['2023-11-12 09:15:32.23', None], dtype='str')
230
+
231
+ with pytest.raises(TypeError):
232
+ try_series_retype(series, 'timestamp')
233
+
234
+
235
+ def test_create_columns_from_df() -> None:
236
+ # test with an empty dataframe
237
+ df = pd.DataFrame()
238
+ with pytest.raises(ValueError):
239
+ create_columns_from_df(df)
240
+
241
+ # test with a dataframe from dictionary
242
+ df = pd.DataFrame(MODEL_DATA)
243
+
244
+ df['Gender'] = df['Gender'].astype('category')
245
+ df['Churned'] = df['Churned'].astype('category')
246
+
247
+ model_schema = create_columns_from_df(df)
248
+ columns = model_schema.columns
249
+ assert len(columns) == 13
250
+ assert columns[0].name == 'Unnamed: 0'
251
+ assert columns[0].data_type == DataType.INTEGER
252
+
253
+ assert columns[1].name == 'CreditScore'
254
+ assert columns[1].data_type == DataType.INTEGER
255
+
256
+ assert columns[2].name == 'Geography'
257
+ assert columns[2].data_type == DataType.STRING
258
+
259
+ assert columns[3].name == 'Gender'
260
+ assert columns[3].data_type == DataType.CATEGORY
261
+
262
+ assert columns[4].name == 'Age'
263
+ assert columns[4].data_type == DataType.INTEGER
264
+
265
+ assert columns[5].name == 'Tenure'
266
+ assert columns[5].data_type == DataType.INTEGER
267
+
268
+ assert columns[6].name == 'Balance'
269
+ assert columns[6].data_type == DataType.FLOAT
270
+
271
+ assert columns[7].name == 'NumOfProducts'
272
+ assert columns[7].data_type == DataType.INTEGER
273
+
274
+ assert columns[8].name == 'HasCrCard'
275
+ assert columns[8].data_type == DataType.BOOLEAN
276
+
277
+ assert columns[9].name == 'IsActiveMember'
278
+ assert columns[9].data_type == DataType.BOOLEAN
279
+
280
+ assert columns[10].name == 'EstimatedSalary'
281
+ assert columns[10].data_type == DataType.FLOAT
282
+
283
+ assert columns[11].name == 'Churned'
284
+ assert columns[11].data_type == DataType.CATEGORY
285
+
286
+ assert columns[12].name == 'probability_churned'
287
+ assert columns[12].data_type == DataType.FLOAT
288
+
289
+ def test_numeric_type_casting():
290
+ # Create a DataFrame with various integer types
291
+ df = pd.DataFrame({
292
+ 'int8_col': pd.Series([1, 2, 3], dtype='int8'),
293
+ 'int16_col': pd.Series([1000, 2000, 3000], dtype='int16'),
294
+ 'int32_col': pd.Series([100000, 200000, 300000], dtype='int32'),
295
+ 'int64_col': pd.Series([10000000000, 20000000000, 30000000000], dtype='int64'),
296
+ 'uint8_col': pd.Series([1, 2, 3], dtype='uint8'),
297
+ 'uint16_col': pd.Series([1000, 2000, 3000], dtype='uint16'),
298
+ 'uint32_col': pd.Series([100000, 200000, 300000], dtype='uint32'),
299
+ 'uint64_col': pd.Series([10000000000, 20000000000, 30000000000], dtype='uint64'),
300
+
301
+ # Float types
302
+ 'float16_col': pd.Series([1.0, 2.0, 3.0], dtype='float16'),
303
+ 'float32_col': pd.Series([1.234, 2.345, 3.456], dtype='float32'),
304
+ 'float64_col': pd.Series([1.23456789, 2.34567890, 3.45678901], dtype='float64'),
305
+
306
+ # Control columns
307
+ 'string_col': pd.Series(['1', '2', '3'], dtype='string'),
308
+ 'bool_col': pd.Series([True, False, True], dtype='bool')
309
+ })
310
+
311
+ # Generate columns from DataFrame
312
+ columns = create_columns_from_df(df).columns
313
+
314
+ # Create a mapping of column names to their expected data types
315
+ expected_types = {
316
+ 'int8_col': DataType.INTEGER,
317
+ 'int16_col': DataType.INTEGER,
318
+ 'int32_col': DataType.INTEGER,
319
+ 'int64_col': DataType.INTEGER,
320
+ 'uint8_col': DataType.INTEGER,
321
+ 'uint16_col': DataType.INTEGER,
322
+ 'uint32_col': DataType.INTEGER,
323
+ 'uint64_col': DataType.INTEGER,
324
+
325
+ # Float columns
326
+ 'float16_col': DataType.FLOAT,
327
+ 'float32_col': DataType.FLOAT,
328
+ 'float64_col': DataType.FLOAT,
329
+
330
+ # control columns
331
+ 'string_col': DataType.STRING,
332
+ 'bool_col': DataType.BOOLEAN
333
+ }
334
+
335
+ # Verify each column's data type
336
+ for column in columns:
337
+ assert column.data_type == expected_types[column.name], \
338
+ f"Column {column.name} should be {expected_types[column.name]}, but got {column.data_type}"
339
+
340
+ # Verify min and max values for numeric columns
341
+ for col_name in df.select_dtypes(include=['int', 'float']).columns:
342
+ col = next(c for c in columns if c.name == col_name)
343
+ assert col.min == df[col_name].min(), \
344
+ f"Min value mismatch for {col_name}: expected {df[col_name].min()}, got {col.min}"
345
+ assert col.max == df[col_name].max(), \
346
+ f"Max value mismatch for {col_name}: expected {df[col_name].max()}, got {col.max}"
347
+
348
+ def test_integer_edge_cases():
349
+ # Test with empty DataFrame
350
+ empty_df = pd.DataFrame({
351
+ 'int_col': pd.Series([], dtype='int64')
352
+ })
353
+ columns = create_columns_from_df(empty_df).columns
354
+ assert len(columns) == 1
355
+ assert columns[0].data_type == DataType.INTEGER
356
+
357
+ # Test with NaN values
358
+ df_with_nan = pd.DataFrame({
359
+ 'int_col': pd.Series([1, 2, np.nan], dtype='float64') # NaN forces float dtype
360
+ })
361
+ columns = create_columns_from_df(df_with_nan).columns
362
+ assert len(columns) == 1
363
+ assert columns[0].data_type == DataType.FLOAT # Should be float due to NaN
364
+
365
+ # Test with mixed types
366
+ df_mixed = pd.DataFrame({
367
+ 'mixed_col': pd.Series([1, '2', 3], dtype='object')
368
+ })
369
+ columns = create_columns_from_df(df_mixed).columns
370
+ assert len(columns) == 1
371
+ assert columns[0].data_type == DataType.STRING # Should be string due to mixed types
372
+
373
+ def test_float_edge_cases():
374
+ # Test with NaN values
375
+ df_nan = pd.DataFrame({
376
+ 'float_nan': pd.Series([1.0, np.nan, 3.0], dtype='float64')
377
+ })
378
+ columns = create_columns_from_df(df_nan).columns
379
+ assert len(columns) == 1
380
+ assert columns[0].data_type == DataType.FLOAT
381
+ assert columns[0].min == 1.0
382
+ assert columns[0].max == 3.0
383
+
384
+ # Test with infinity
385
+ df_inf = pd.DataFrame({
386
+ 'float_inf': pd.Series([1.0, np.inf, -np.inf], dtype='float64')
387
+ })
388
+ columns = create_columns_from_df(df_inf).columns
389
+ assert len(columns) == 1
390
+ assert columns[0].data_type == DataType.FLOAT
391
+ assert columns[0].min == -np.inf
392
+ assert columns[0].max == np.inf
393
+
394
+ # Test with very small numbers
395
+ df_small = pd.DataFrame({
396
+ 'float_small': pd.Series([1e-10, 1e-20, 1e-30], dtype='float64')
397
+ })
398
+ columns = create_columns_from_df(df_small).columns
399
+ assert len(columns) == 1
400
+ assert columns[0].data_type == DataType.FLOAT
401
+ assert columns[0].min == 1e-30
402
+ assert columns[0].max == 1e-10
403
+
404
+ # Test with very large numbers
405
+ df_large = pd.DataFrame({
406
+ 'float_large': pd.Series([1e10, 1e20, 1e30], dtype='float64')
407
+ })
408
+ columns = create_columns_from_df(df_large).columns
409
+ assert len(columns) == 1
410
+ assert columns[0].data_type == DataType.FLOAT
411
+ assert columns[0].min == 1e10
412
+ assert columns[0].max == 1e30
413
+
414
+ def test_mixed_numeric_types():
415
+ # Test with mixed integer and float values
416
+ df_mixed = pd.DataFrame({
417
+ 'mixed_numeric': pd.Series([1, 2.0, 3], dtype='float64')
418
+ })
419
+ columns = create_columns_from_df(df_mixed).columns
420
+ assert len(columns) == 1
421
+ assert columns[0].data_type == DataType.FLOAT
422
+ assert columns[0].min == 1.0
423
+ assert columns[0].max == 3.0
424
+
425
+ # Test with mixed numeric and string values
426
+ df_mixed_str = pd.DataFrame({
427
+ 'mixed_str': pd.Series([1, '2.0', 3], dtype='object')
428
+ })
429
+ columns = create_columns_from_df(df_mixed_str).columns
430
+ assert len(columns) == 1
431
+ assert columns[0].data_type == DataType.STRING # Should be string due to mixed types
@@ -0,0 +1,68 @@
1
+ from typing import List
2
+ import pandas as pd
3
+ from fiddler.schemas.model_schema import Column, ModelSchema
4
+ from fiddler.constants.model import DataType
5
+
6
+
7
+ def create_columns_from_df(df: pd.DataFrame) -> ModelSchema:
8
+ """
9
+ Helper function to create Columns from a pandas DataFrame column dtypes.
10
+ timedelta, period, interval & object dtypes are converted to string.
11
+ Sparse dtypes are not handled.
12
+ Args:
13
+ df: Input pandas DataFrame
14
+
15
+ Returns:
16
+ ModelSchema
17
+ """
18
+ columns: List[Column] = []
19
+
20
+ # Check if the DataFrame is empty - a dataframe with columns but no rows is valid
21
+ if len(df.columns) == 0:
22
+ raise ValueError("Cannot create columns from an empty DataFrame")
23
+
24
+ for col_name, dtype in df.dtypes.items():
25
+ # Map pandas dtypes to Fiddler DataTypes
26
+ if pd.api.types.is_float_dtype(dtype):
27
+ data_type = DataType.FLOAT
28
+ col_min, col_max = float(df[col_name].min()), float(df[col_name].max())
29
+ col = Column(
30
+ name=col_name,
31
+ data_type=data_type,
32
+ min=col_min,
33
+ max=col_max
34
+ )
35
+ elif pd.api.types.is_integer_dtype(dtype):
36
+ data_type = DataType.INTEGER
37
+ col = Column(
38
+ name=col_name,
39
+ data_type=data_type,
40
+ min=df[col_name].min(),
41
+ max=df[col_name].max()
42
+ )
43
+ elif pd.api.types.is_bool_dtype(dtype):
44
+ col = Column(
45
+ name=col_name,
46
+ data_type=DataType.BOOLEAN
47
+ )
48
+ elif pd.api.types.is_datetime64_any_dtype(dtype):
49
+ col = Column(
50
+ name=col_name,
51
+ data_type=DataType.TIMESTAMP
52
+ )
53
+ elif pd.api.types.is_categorical_dtype(dtype):
54
+ col = Column(
55
+ name=col_name,
56
+ data_type=DataType.CATEGORY,
57
+ categories=df[col_name].cat.categories.tolist()
58
+ )
59
+ else:
60
+ # Default to string for other types
61
+ col = Column(
62
+ name=col_name,
63
+ data_type=DataType.STRING
64
+ )
65
+
66
+ columns.append(col)
67
+
68
+ return ModelSchema(columns=columns)
@@ -73,6 +73,7 @@ class ModelGenerator(ConnectionMixin):
73
73
  url='/v3/model-factory',
74
74
  data=payload,
75
75
  headers={'Content-Type': 'application/json'},
76
+ timeout=600,
76
77
  )
77
78
 
78
79
  return GenerateModelResp(**response.json()['data'])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fiddler-client
3
- Version: 3.8.2
3
+ Version: 3.9.0
4
4
  Summary: Python client for Fiddler Platform
5
5
  Home-page: https://fiddler.ai
6
6
  Author: Fiddler Labs
@@ -103,6 +103,7 @@ fiddler/tests/apis/test_segment.py
103
103
  fiddler/tests/apis/test_webhook.py
104
104
  fiddler/tests/apis/test_xai.py
105
105
  fiddler/utils/__init__.py
106
+ fiddler/utils/column_generator.py
106
107
  fiddler/utils/decorators.py
107
108
  fiddler/utils/helpers.py
108
109
  fiddler/utils/logger.py
@@ -1,4 +1,4 @@
1
- deprecated==1.2.15
1
+ deprecated==1.2.18
2
2
  pandas>=1.2.5
3
3
  pip>=21.0
4
4
  pyarrow>=7.0.0
@@ -29,7 +29,7 @@ setuptools.setup(
29
29
  'requests-toolbelt',
30
30
  'pandas>=1.2.5',
31
31
  'pydantic>=1.10.17',
32
- 'deprecated==1.2.15',
32
+ 'deprecated==1.2.18',
33
33
  'tqdm',
34
34
  'simplejson>=3.17.0',
35
35
  'pyarrow>=7.0.0',
@@ -1 +0,0 @@
1
- 3.8.2
@@ -1,99 +0,0 @@
1
- import os
2
- import tempfile
3
- from pathlib import Path
4
-
5
- import pandas as pd
6
- import pytest
7
-
8
- from fiddler.schemas.server_info import Version
9
- from fiddler.utils.helpers import group_by, try_series_retype
10
- from fiddler.utils.validations import validate_artifact_dir
11
- from fiddler.utils.version import match_semver
12
-
13
-
14
- def test_match_semvar_version() -> None:
15
- assert match_semver(None, '>=22.9.0') is False
16
- assert match_semver(Version.parse('22.9.0'), '>=22.10.0') is False
17
- assert match_semver(Version.parse('22.10.0'), '>=22.10.0') is True
18
- assert match_semver(Version.parse('22.10.0'), '>22.10.0') is False
19
- assert match_semver(Version.parse('22.11.0'), '>=22.10.0') is True
20
- assert match_semver(Version.parse('22.11.0'), '>22.10.0') is True
21
- assert match_semver(Version.parse('22.10.0'), '<=22.10.0') is True
22
- assert match_semver(Version.parse('22.10.0'), '<22.10.0') is False
23
- assert match_semver(Version.parse('22.9.0'), '<22.10.0') is True
24
- assert match_semver(Version.parse('22.11.0-RC1'), '>=22.11.0') is True
25
-
26
-
27
- def test_validate_artifact_dir(tmp_path) -> None:
28
- artifact_dir = os.path.join(Path(__file__).resolve().parent, 'artifact_test_dir')
29
- assert validate_artifact_dir(Path(artifact_dir)) is None
30
- # Test for artifact_dir not valid directory
31
- with pytest.raises(ValueError):
32
- validate_artifact_dir(Path('test'))
33
- # Test for package.py file not found
34
- mock_dir = tmp_path / 'test'
35
- mock_dir.mkdir()
36
- with pytest.raises(ValueError):
37
- validate_artifact_dir(Path(mock_dir))
38
-
39
-
40
- def test_group_by_helper() -> None:
41
- df = pd.DataFrame(
42
- [
43
- {'col1': 1, 'col2': 'foo'},
44
- {'col1': 2, 'col2': 'bar'},
45
- {'col1': 3, 'col2': 'baz'},
46
- {'col1': 3, 'col2': 'foo'},
47
- ]
48
- )
49
-
50
- # with output_path
51
- with tempfile.TemporaryDirectory() as temp_dir:
52
- file_path = Path(temp_dir) / 'data.csv'
53
-
54
- assert file_path.exists() is False
55
- group_by(df=df, group_by_col='col2', output_path=file_path)
56
- assert file_path.exists() is True
57
-
58
- # without output path
59
- grouped_df = group_by(df=df, group_by_col='col2')
60
- assert grouped_df.equals(
61
- pd.DataFrame(
62
- [
63
- {'col2': 'foo', 'col1': [1, 3]},
64
- {'col2': 'bar', 'col1': [2]},
65
- {'col2': 'baz', 'col1': [3]},
66
- ]
67
- )
68
- )
69
-
70
-
71
- def test_try_series_retype() -> None:
72
- series = pd.Series([1, 2, 3], dtype='float')
73
- series = try_series_retype(series, 'int')
74
- assert series.dtype == 'int'
75
-
76
- series = pd.Series([1, 2, None])
77
- series = try_series_retype(series, 'int')
78
- assert series.dtype == 'float'
79
-
80
-
81
- def test_try_series_retype_str_or_unkown() -> None:
82
- series = pd.Series(['HIGH', 'MEDIUM', '', None], dtype='str')
83
- series = try_series_retype(series, 'str')
84
- assert series.dtype == 'object'
85
-
86
-
87
- def test_try_series_retype_timestamp() -> None:
88
- series = pd.Series(
89
- ['2023-11-12 09:15:32.23', '2023-12-11 09:15:32.45'], dtype='str'
90
- )
91
- series = try_series_retype(series, 'timestamp')
92
- assert series.dtype == 'datetime64[ns]'
93
-
94
-
95
- def test_try_series_retype_timestamp_error() -> None:
96
- series = pd.Series(['2023-11-12 09:15:32.23', None], dtype='str')
97
-
98
- with pytest.raises(TypeError):
99
- try_series_retype(series, 'timestamp')
File without changes
File without changes
File without changes