fiddler-client 3.12.0rc1__tar.gz → 3.13.0.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. {fiddler_client-3.12.0rc1/fiddler_client.egg-info → fiddler_client-3.13.0.dev1}/PKG-INFO +1 -1
  2. fiddler_client-3.13.0.dev1/fiddler/VERSION +1 -0
  3. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/connection.py +5 -5
  4. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/events.py +98 -1
  5. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/model.py +66 -0
  6. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/custom_features.py +1 -1
  7. fiddler_client-3.13.0.dev1/fiddler/schemas/events.py +42 -0
  8. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_events.py +240 -0
  9. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1/fiddler_client.egg-info}/PKG-INFO +1 -1
  10. fiddler_client-3.12.0rc1/fiddler/VERSION +0 -1
  11. fiddler_client-3.12.0rc1/fiddler/schemas/events.py +0 -15
  12. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/LICENSE.txt +0 -0
  13. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/PUBLIC.md +0 -0
  14. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/README.md +0 -0
  15. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/__init__.py +0 -0
  16. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/configs.py +0 -0
  17. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/__init__.py +0 -0
  18. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/alert_rule.py +0 -0
  19. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/baseline.py +0 -0
  20. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/common.py +0 -0
  21. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/dataset.py +0 -0
  22. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/events.py +0 -0
  23. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/job.py +0 -0
  24. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/model.py +0 -0
  25. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/model_deployment.py +0 -0
  26. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/constants/xai.py +0 -0
  27. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/decorators.py +0 -0
  28. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/__init__.py +0 -0
  29. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/alert_record.py +0 -0
  30. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/alert_rule.py +0 -0
  31. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/base.py +0 -0
  32. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/baseline.py +0 -0
  33. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/custom_expression.py +0 -0
  34. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/dataset.py +0 -0
  35. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/file.py +0 -0
  36. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/job.py +0 -0
  37. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/model_artifact.py +0 -0
  38. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/model_deployment.py +0 -0
  39. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/organization.py +0 -0
  40. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/project.py +0 -0
  41. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/surrogate.py +0 -0
  42. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/user.py +0 -0
  43. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/webhook.py +0 -0
  44. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/entities/xai.py +0 -0
  45. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/exceptions.py +0 -0
  46. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/libs/__init__.py +0 -0
  47. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/libs/aws.py +0 -0
  48. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/libs/http_client.py +0 -0
  49. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/libs/json_encoder.py +0 -0
  50. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/libs/semver.py +0 -0
  51. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/packtools/__init__.py +0 -0
  52. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/packtools/gem.py +0 -0
  53. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/packtools/keras_ig_helpers.py +0 -0
  54. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/packtools/project_attributions_helpers.py +0 -0
  55. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/packtools/template_model.py +0 -0
  56. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/__init__.py +0 -0
  57. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/alert_record.py +0 -0
  58. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/alert_rule.py +0 -0
  59. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/base.py +0 -0
  60. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/baseline.py +0 -0
  61. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/custom_expression.py +0 -0
  62. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/dataset.py +0 -0
  63. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/file.py +0 -0
  64. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/filter_query.py +0 -0
  65. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/job.py +0 -0
  66. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/model.py +0 -0
  67. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/model_deployment.py +0 -0
  68. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/model_schema.py +0 -0
  69. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/model_spec.py +0 -0
  70. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/model_task_params.py +0 -0
  71. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/organization.py +0 -0
  72. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/project.py +0 -0
  73. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/response.py +0 -0
  74. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/server_info.py +0 -0
  75. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/user.py +0 -0
  76. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/webhook.py +0 -0
  77. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/xai.py +0 -0
  78. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/schemas/xai_params.py +0 -0
  79. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/__init__.py +0 -0
  80. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/__init__.py +0 -0
  81. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_alert_record.py +0 -0
  82. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_alert_rule.py +0 -0
  83. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_baseline.py +0 -0
  84. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_custom_metric.py +0 -0
  85. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_dataset.py +0 -0
  86. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_files.py +0 -0
  87. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_generate_model.py +0 -0
  88. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_job.py +0 -0
  89. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_mixin.py +0 -0
  90. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_model.py +0 -0
  91. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_model_artifact.py +0 -0
  92. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_model_deployment.py +0 -0
  93. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_model_surrogate.py +0 -0
  94. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_project.py +0 -0
  95. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_segment.py +0 -0
  96. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_webhook.py +0 -0
  97. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/apis/test_xai.py +0 -0
  98. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/artifact_test_dir/package.py +0 -0
  99. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/conftest.py +0 -0
  100. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/constants.py +0 -0
  101. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/test_connection.py +0 -0
  102. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/test_json_encoder.py +0 -0
  103. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/test_logger.py +0 -0
  104. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/test_sagemaker_auth.py +0 -0
  105. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/test_utils.py +0 -0
  106. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/tests/utils.py +0 -0
  107. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/__init__.py +0 -0
  108. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/column_generator.py +0 -0
  109. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/decorators.py +0 -0
  110. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/helpers.py +0 -0
  111. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/logger.py +0 -0
  112. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/model_generator.py +0 -0
  113. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/validations.py +0 -0
  114. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/utils/version.py +0 -0
  115. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler/version.py +0 -0
  116. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler_client.egg-info/SOURCES.txt +0 -0
  117. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler_client.egg-info/dependency_links.txt +0 -0
  118. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler_client.egg-info/requires.txt +0 -0
  119. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/fiddler_client.egg-info/top_level.txt +0 -0
  120. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/pyproject.toml +0 -0
  121. {fiddler_client-3.12.0rc1 → fiddler_client-3.13.0.dev1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiddler-client
3
- Version: 3.12.0rc1
3
+ Version: 3.13.0.dev1
4
4
  Summary: Python client for Fiddler Platform
5
5
  Author: Fiddler Labs
6
6
  License-Expression: Apache-2.0
@@ -0,0 +1 @@
1
+ 3.13.0.dev1
@@ -52,14 +52,14 @@ class Connection:
52
52
 
53
53
  connection = Connection(
54
54
  url="https://your-fiddler-instance.com",
55
- token="your-auth-token"
55
+ token="your-api-key"
56
56
  )
57
57
 
58
58
  Creating a connection with custom timeout and proxy:
59
59
 
60
60
  connection = Connection(
61
61
  url="https://your-fiddler-instance.com",
62
- token="your-auth-token",
62
+ token="your-api-key",
63
63
  timeout=(5.0, 30.0), # (connect_timeout, read_timeout)
64
64
  proxies={"https": "https://proxy.company.com:8080"}
65
65
  )
@@ -68,7 +68,7 @@ class Connection:
68
68
 
69
69
  connection = Connection(
70
70
  url="https://your-fiddler-instance.com",
71
- token="your-auth-token",
71
+ token="your-api-key",
72
72
  verify=False, # Not recommended for production
73
73
  validate=False # Skip version compatibility check
74
74
  )
@@ -365,14 +365,14 @@ def init( # pylint: disable=too-many-arguments
365
365
 
366
366
  fdl.init(
367
367
  url="https://your-fiddler-instance.com",
368
- token="your-auth-token"
368
+ token="your-api-key"
369
369
  )
370
370
 
371
371
  Initialization with custom timeout and proxy:
372
372
 
373
373
  fdl.init(
374
374
  url="https://your-fiddler-instance.com",
375
- token="your-auth-token",
375
+ token="your-api-key",
376
376
  timeout=(10.0, 60.0), # 10s connect, 60s read timeout
377
377
  proxies={"https": "https://proxy.company.com:8080"}
378
378
  )
@@ -4,6 +4,7 @@ import logging
4
4
  import os
5
5
  import tempfile
6
6
  import warnings
7
+ from datetime import datetime
7
8
  from pathlib import Path
8
9
  from typing import Any
9
10
  from uuid import UUID
@@ -18,11 +19,16 @@ from fiddler.decorators import handle_api_error
18
19
  from fiddler.entities.file import File
19
20
  from fiddler.entities.job import Job
20
21
  from fiddler.schemas.dataset import EnvType
21
- from fiddler.schemas.events import EventsSource, FileSource
22
+ from fiddler.schemas.events import EventsSource, FileSource, TimeRange
22
23
  from fiddler.schemas.job import JobCompactResp
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
 
27
+ # Maximum number of event ids accepted by the delete events API in a single
28
+ # call, as documented at
29
+ # https://docs.fiddler.ai/sdk-api/rest-api/events/delete-events
30
+ DELETE_EVENT_IDS_LIMIT = 10_000
31
+
26
32
 
27
33
  class EventPublisher(ConnectionMixin):
28
34
  STREAM_LIMIT = STREAM_EVENT_LIMIT
@@ -141,6 +147,97 @@ class EventPublisher(ConnectionMixin):
141
147
  update=update,
142
148
  )
143
149
 
150
+ @handle_api_error
151
+ def delete_events(
152
+ self,
153
+ start_time: datetime | str | None = None,
154
+ end_time: datetime | str | None = None,
155
+ event_ids: list[str] | None = None,
156
+ update_metrics: bool = False,
157
+ ) -> Job:
158
+ """
159
+ Delete published production events for this model.
160
+
161
+ Events can be selected by a time range, by an explicit list of event
162
+ ids, or by both. Deletion runs asynchronously on the server; use the
163
+ returned :class:`~fiddler.entities.job.Job` to track progress.
164
+
165
+ AWS SageMaker SigV4 request signing (when configured via the
166
+ ``AWS_PARTNER_APP_*`` environment variables) is applied automatically,
167
+ as it is for every other client request.
168
+
169
+ :param start_time: Inclusive start of the time window to delete. Accepts
170
+ a timezone-aware ``datetime`` or an ISO 8601 string with an explicit
171
+ offset (e.g. ``'2025-11-17T01:00:00Z'``); the value is normalized to
172
+ UTC. Naive datetimes are rejected. Must be given together with
173
+ ``end_time``.
174
+ :param end_time: Exclusive end of the time window to delete. Same format
175
+ rules as ``start_time``. Must be given together with ``start_time``.
176
+ :param event_ids: List of up to ``DELETE_EVENT_IDS_LIMIT`` event ids to
177
+ delete. An empty list is treated as "no id filter" (the same as
178
+ ``None``), so a programmatically built list that comes back empty
179
+ still works alongside a time range.
180
+ :param update_metrics: Whether the server should recompute metrics after
181
+ the events are deleted.
182
+
183
+ :return: Job object for tracking the async deletion.
184
+
185
+ :raises ValueError: If neither a time range nor ``event_ids`` is given
186
+ (to avoid accidentally deleting all events for the model), if only
187
+ one of ``start_time``/``end_time`` is provided, or if ``event_ids``
188
+ exceeds ``DELETE_EVENT_IDS_LIMIT``.
189
+ :raises pydantic.ValidationError: If ``start_time`` or ``end_time`` is a
190
+ naive datetime (without timezone information).
191
+ """
192
+ # Treat an empty event_ids list as "no id filter" so a programmatically
193
+ # built (possibly empty) list still works alongside a time range,
194
+ # rather than surfacing a surprising ValueError.
195
+ if event_ids is not None and len(event_ids) == 0:
196
+ event_ids = None
197
+
198
+ if (start_time is None) != (end_time is None):
199
+ raise ValueError(
200
+ 'start_time and end_time must be provided together to delete '
201
+ 'events by time range'
202
+ )
203
+
204
+ if start_time is None and event_ids is None:
205
+ raise ValueError(
206
+ 'Provide a time range (start_time and end_time) and/or '
207
+ 'event_ids. Refusing to delete all events for the model.'
208
+ )
209
+
210
+ if event_ids is not None and len(event_ids) > DELETE_EVENT_IDS_LIMIT:
211
+ raise ValueError(
212
+ f'event_ids must contain at most {DELETE_EVENT_IDS_LIMIT:,} '
213
+ f'ids, got {len(event_ids)}'
214
+ )
215
+
216
+ body: dict[str, Any] = {
217
+ 'model_id': self.model_id,
218
+ 'update_metrics': update_metrics,
219
+ }
220
+
221
+ if start_time is not None:
222
+ # Serialize to JSON-native types (ISO 8601 strings) so the request
223
+ # body matches the format documented for the delete events API.
224
+ body['time_range'] = TimeRange(
225
+ start_time=start_time, # type: ignore[arg-type]
226
+ end_time=end_time, # type: ignore[arg-type]
227
+ ).model_dump(mode='json')
228
+
229
+ if event_ids is not None:
230
+ body['event_ids'] = event_ids
231
+
232
+ response = self._client().delete(
233
+ url='/v3/events',
234
+ data=body,
235
+ headers={'Content-Type': 'application/json'},
236
+ )
237
+
238
+ job_compact = JobCompactResp(**response.json()['data']['job'])
239
+ return Job.get(id_=job_compact.id)
240
+
144
241
  def _publish_batch(
145
242
  self,
146
243
  source: str | Path | pd.DataFrame,
@@ -1296,6 +1296,72 @@ class Model( # pylint: disable=too-many-public-methods
1296
1296
  update=update,
1297
1297
  )
1298
1298
 
1299
+ @handle_api_error
1300
+ def delete_events(
1301
+ self,
1302
+ start_time: datetime | str | None = None,
1303
+ end_time: datetime | str | None = None,
1304
+ event_ids: builtins.list[str] | None = None,
1305
+ update_metrics: bool = False,
1306
+ ) -> Job:
1307
+ """Delete published production events for this model.
1308
+
1309
+ Select events to delete by a time range, by an explicit list of event
1310
+ ids, or by both. Deletion runs asynchronously on the server; use the
1311
+ returned Job to track progress.
1312
+
1313
+ Args:
1314
+ start_time: Inclusive start of the time window to delete. Accepts a
1315
+ timezone-aware ``datetime`` or an ISO 8601 string with an
1316
+ explicit offset (e.g. ``'2025-11-17T01:00:00Z'``); the
1317
+ value is normalized to UTC. Naive datetimes are rejected.
1318
+ Must be given together with ``end_time``.
1319
+ end_time: Exclusive end of the time window to delete. Same format
1320
+ rules as ``start_time``. Must be given together with
1321
+ ``start_time``.
1322
+ event_ids: List of event ids to delete, up to the documented
1323
+ per-call limit. An empty list is treated as "no id
1324
+ filter" (the same as ``None``).
1325
+ update_metrics: Whether the server should recompute metrics after
1326
+ the events are deleted.
1327
+
1328
+ Returns:
1329
+ :class:`~fiddler.entities.Job`: Async job object. Use ``job.wait()``
1330
+ to block until completion, or check ``job.status`` to poll.
1331
+
1332
+ Raises:
1333
+ ValueError: If neither a time range nor ``event_ids`` is given (to
1334
+ avoid accidentally deleting all events for the model), if
1335
+ only one of ``start_time``/``end_time`` is provided, or if
1336
+ ``event_ids`` exceeds the documented per-call limit.
1337
+ ApiError: If there's an error deleting the events on Fiddler.
1338
+
1339
+ Example:
1340
+ # Delete a window of production events
1341
+ job = model.delete_events(
1342
+ start_time='2025-11-17T01:00:00.000Z',
1343
+ end_time='2025-11-17T02:00:00.000Z',
1344
+ update_metrics=True,
1345
+ )
1346
+ job.wait()
1347
+ print(f"Delete status: {job.status}")
1348
+
1349
+ # Delete specific events by id
1350
+ job = model.delete_events(event_ids=['pred_001', 'pred_002'])
1351
+ job.wait()
1352
+
1353
+ Note:
1354
+ Deletion is irreversible. Provide a time range and/or ``event_ids``
1355
+ to scope the deletion; a call with no filter is rejected.
1356
+ """
1357
+ logger.info('Model[%s/%s] - Deleting events', self.name, self.version)
1358
+ return self._event_publisher.delete_events(
1359
+ start_time=start_time,
1360
+ end_time=end_time,
1361
+ event_ids=event_ids,
1362
+ update_metrics=update_metrics,
1363
+ )
1364
+
1299
1365
  def add_artifact(
1300
1366
  self,
1301
1367
  model_dir: str | Path,
@@ -298,7 +298,7 @@ class ImageEmbedding(VectorFeature):
298
298
 
299
299
 
300
300
  class Enrichment(CustomFeature):
301
- """Represents custom features derived from enrichment operations (Private Preview).
301
+ """Represents custom features derived from enrichment operations.
302
302
 
303
303
  Enrichment features apply external processing or analysis to existing columns
304
304
  to create derived insights. This can include operations like sentiment analysis,
@@ -0,0 +1,42 @@
1
+ from datetime import datetime, timezone
2
+ from typing import Any, Dict, List
3
+ from uuid import UUID
4
+
5
+ from pydantic import AwareDatetime, field_validator
6
+
7
+ from fiddler.constants.events import PublishEventsSourceType
8
+ from fiddler.schemas.base import BaseModel
9
+
10
+
11
+ class FileSource(BaseModel):
12
+ file_id: UUID
13
+ type: PublishEventsSourceType = PublishEventsSourceType.FILE
14
+
15
+
16
+ class EventsSource(BaseModel):
17
+ events: List[Dict[Any, Any]]
18
+ type: PublishEventsSourceType = PublishEventsSourceType.EVENTS
19
+
20
+
21
+ class TimeRange(BaseModel):
22
+ """Half-open time window used to filter events for deletion.
23
+
24
+ ``start_time`` and ``end_time`` accept timezone-aware
25
+ :class:`~datetime.datetime` objects or ISO 8601 strings that carry an
26
+ explicit offset (e.g. ``'2025-11-17T01:00:00Z'`` or
27
+ ``'2025-11-17T01:00:00+05:00'``). Aware values are normalized to UTC, so
28
+ ``model_dump(mode='json')`` always emits a ``Z``-suffixed UTC timestamp.
29
+
30
+ Naive datetimes (without timezone information) are rejected with a
31
+ ``ValidationError`` rather than guessed at — for a destructive operation the
32
+ deleted window must never be ambiguous.
33
+ """
34
+
35
+ start_time: AwareDatetime
36
+ end_time: AwareDatetime
37
+
38
+ @field_validator('start_time', 'end_time', mode='after')
39
+ @classmethod
40
+ def _normalize_to_utc(cls, value: datetime) -> datetime:
41
+ """Convert any aware datetime to UTC for a consistent wire format."""
42
+ return value.astimezone(timezone.utc)
@@ -1,14 +1,19 @@
1
+ import json
1
2
  import warnings
3
+ from datetime import datetime, timezone
2
4
  from pathlib import Path
3
5
  from uuid import uuid4
4
6
  import re
5
7
 
6
8
  import numpy as np
7
9
  import pandas as pd
10
+ import pytest
8
11
  import responses
9
12
  import requests
13
+ from pydantic import ValidationError
10
14
 
11
15
  from fiddler.constants.dataset import EnvType
16
+ from fiddler.entities.events import EventPublisher
12
17
  from fiddler.entities.job import Job
13
18
  from fiddler.entities.model import Model
14
19
  from fiddler.tests.apis.test_files import SINGLE_UPLOAD_200_RESPONSE
@@ -51,6 +56,14 @@ PUBLISH_JOB_RESPONSE = {
51
56
  'kind': 'NORMAL',
52
57
  }
53
58
 
59
+ DELETE_EVENTS_202_API_RESPONSE = {
60
+ 'data': {
61
+ 'job': {'id': JOB_ID, 'name': 'Delete events'},
62
+ },
63
+ 'api_version': '3.0',
64
+ 'kind': 'NORMAL',
65
+ }
66
+
54
67
  df = pd.DataFrame(np.random.randint(0, 100, size=(10, 4)), columns=list('ABCD'))
55
68
 
56
69
  STREAM_PUBLISH_202_API_RESPONSE = {
@@ -395,3 +408,230 @@ def test_publish_deprecated() -> None:
395
408
  assert len(w) == 1
396
409
  assert issubclass(w[0].category, DeprecationWarning)
397
410
  assert 'publish() is deprecated' in str(w[0].message)
411
+
412
+
413
+ @responses.activate
414
+ def test_delete_events_by_time_range() -> None:
415
+ responses.get(
416
+ url=f'{URL}/v3/models/{MODEL_ID}',
417
+ json=MODEL_API_RESPONSE_200,
418
+ )
419
+ model = Model.get(id_=MODEL_ID)
420
+
421
+ def _body_matcher(r: requests.PreparedRequest) -> tuple[bool, str]:
422
+ body = json.loads(r.body)
423
+ if 'time_range' not in body:
424
+ return False, f'time_range missing from body: {body}'
425
+ if body['time_range']['start_time'] != '2025-11-17T01:00:00Z':
426
+ return False, f'unexpected start_time: {body}'
427
+ if body.get('update_metrics') is not True:
428
+ return False, f'update_metrics not forwarded: {body}'
429
+ return True, 'good'
430
+
431
+ responses.delete(
432
+ url=f'{URL}/v3/events',
433
+ json=DELETE_EVENTS_202_API_RESPONSE,
434
+ match=[_body_matcher],
435
+ )
436
+ responses.get(
437
+ url=f'{URL}/v3/jobs/{JOB_ID}',
438
+ json=PUBLISH_JOB_RESPONSE,
439
+ )
440
+
441
+ job = model.delete_events(
442
+ start_time=datetime(2025, 11, 17, 1, 0, 0, tzinfo=timezone.utc),
443
+ end_time=datetime(2025, 11, 17, 2, 0, 0, tzinfo=timezone.utc),
444
+ update_metrics=True,
445
+ )
446
+
447
+ assert isinstance(job, Job)
448
+
449
+
450
+ @responses.activate
451
+ def test_delete_events_by_event_ids() -> None:
452
+ responses.get(
453
+ url=f'{URL}/v3/models/{MODEL_ID}',
454
+ json=MODEL_API_RESPONSE_200,
455
+ )
456
+ model = Model.get(id_=MODEL_ID)
457
+
458
+ responses.delete(
459
+ url=f'{URL}/v3/events',
460
+ json=DELETE_EVENTS_202_API_RESPONSE,
461
+ )
462
+ responses.get(
463
+ url=f'{URL}/v3/jobs/{JOB_ID}',
464
+ json=PUBLISH_JOB_RESPONSE,
465
+ )
466
+
467
+ job = model.delete_events(event_ids=['pred_001', 'pred_002'])
468
+
469
+ assert isinstance(job, Job)
470
+
471
+
472
+ @responses.activate
473
+ def test_delete_events_accepts_iso_string() -> None:
474
+ responses.get(
475
+ url=f'{URL}/v3/models/{MODEL_ID}',
476
+ json=MODEL_API_RESPONSE_200,
477
+ )
478
+ model = Model.get(id_=MODEL_ID)
479
+
480
+ responses.delete(
481
+ url=f'{URL}/v3/events',
482
+ json=DELETE_EVENTS_202_API_RESPONSE,
483
+ )
484
+ responses.get(
485
+ url=f'{URL}/v3/jobs/{JOB_ID}',
486
+ json=PUBLISH_JOB_RESPONSE,
487
+ )
488
+
489
+ job = model.delete_events(
490
+ start_time='2025-11-17T01:00:00.000Z',
491
+ end_time='2025-11-17T02:00:00.000Z',
492
+ )
493
+
494
+ assert isinstance(job, Job)
495
+
496
+
497
+ @responses.activate
498
+ def test_delete_events_by_time_range_and_event_ids() -> None:
499
+ responses.get(
500
+ url=f'{URL}/v3/models/{MODEL_ID}',
501
+ json=MODEL_API_RESPONSE_200,
502
+ )
503
+ model = Model.get(id_=MODEL_ID)
504
+
505
+ def _body_matcher(r: requests.PreparedRequest) -> tuple[bool, str]:
506
+ body = json.loads(r.body)
507
+ if 'time_range' not in body:
508
+ return False, f'time_range missing from body: {body}'
509
+ if body.get('event_ids') != ['pred_001']:
510
+ return False, f'event_ids missing/incorrect in body: {body}'
511
+ return True, 'good'
512
+
513
+ responses.delete(
514
+ url=f'{URL}/v3/events',
515
+ json=DELETE_EVENTS_202_API_RESPONSE,
516
+ match=[_body_matcher],
517
+ )
518
+ responses.get(
519
+ url=f'{URL}/v3/jobs/{JOB_ID}',
520
+ json=PUBLISH_JOB_RESPONSE,
521
+ )
522
+
523
+ job = model.delete_events(
524
+ start_time='2025-11-17T01:00:00.000Z',
525
+ end_time='2025-11-17T02:00:00.000Z',
526
+ event_ids=['pred_001'],
527
+ )
528
+
529
+ assert isinstance(job, Job)
530
+
531
+
532
+ @responses.activate
533
+ def test_delete_events_empty_event_ids_with_time_range() -> None:
534
+ # An empty event_ids list is treated as "no id filter", so a delete scoped
535
+ # by time range still succeeds and the body omits event_ids.
536
+ responses.get(
537
+ url=f'{URL}/v3/models/{MODEL_ID}',
538
+ json=MODEL_API_RESPONSE_200,
539
+ )
540
+ model = Model.get(id_=MODEL_ID)
541
+
542
+ def _body_matcher(r: requests.PreparedRequest) -> tuple[bool, str]:
543
+ body = json.loads(r.body)
544
+ if 'event_ids' in body:
545
+ return False, f'event_ids should be omitted for empty list: {body}'
546
+ if 'time_range' not in body:
547
+ return False, f'time_range missing from body: {body}'
548
+ return True, 'good'
549
+
550
+ responses.delete(
551
+ url=f'{URL}/v3/events',
552
+ json=DELETE_EVENTS_202_API_RESPONSE,
553
+ match=[_body_matcher],
554
+ )
555
+ responses.get(
556
+ url=f'{URL}/v3/jobs/{JOB_ID}',
557
+ json=PUBLISH_JOB_RESPONSE,
558
+ )
559
+
560
+ job = model.delete_events(
561
+ start_time='2025-11-17T01:00:00.000Z',
562
+ end_time='2025-11-17T02:00:00.000Z',
563
+ event_ids=[],
564
+ )
565
+
566
+ assert isinstance(job, Job)
567
+
568
+
569
+ @responses.activate
570
+ def test_delete_events_normalizes_non_utc_time_range() -> None:
571
+ # A non-UTC offset must be converted to UTC on the wire: 01:00+05:00 is
572
+ # 20:00Z on the previous day.
573
+ responses.get(
574
+ url=f'{URL}/v3/models/{MODEL_ID}',
575
+ json=MODEL_API_RESPONSE_200,
576
+ )
577
+ model = Model.get(id_=MODEL_ID)
578
+
579
+ def _body_matcher(r: requests.PreparedRequest) -> tuple[bool, str]:
580
+ body = json.loads(r.body)
581
+ start = body['time_range']['start_time']
582
+ if start != '2025-11-16T20:00:00Z':
583
+ return False, f'start_time not normalized to UTC: {start!r}'
584
+ return True, 'good'
585
+
586
+ responses.delete(
587
+ url=f'{URL}/v3/events',
588
+ json=DELETE_EVENTS_202_API_RESPONSE,
589
+ match=[_body_matcher],
590
+ )
591
+ responses.get(
592
+ url=f'{URL}/v3/jobs/{JOB_ID}',
593
+ json=PUBLISH_JOB_RESPONSE,
594
+ )
595
+
596
+ job = model.delete_events(
597
+ start_time='2025-11-17T01:00:00+05:00',
598
+ end_time='2025-11-17T02:00:00+05:00',
599
+ )
600
+
601
+ assert isinstance(job, Job)
602
+
603
+
604
+ def test_delete_events_rejects_naive_datetime() -> None:
605
+ with pytest.raises(ValidationError):
606
+ EventPublisher(model_id=MODEL_ID).delete_events(
607
+ start_time=datetime(2025, 11, 17, 1, 0, 0),
608
+ end_time=datetime(2025, 11, 17, 2, 0, 0),
609
+ )
610
+
611
+
612
+ def test_delete_events_requires_a_filter() -> None:
613
+ # Use the publisher directly so the client-side guard is exercised without
614
+ # an HTTP round-trip.
615
+ with pytest.raises(ValueError, match='Refusing to delete all events'):
616
+ EventPublisher(model_id=MODEL_ID).delete_events()
617
+
618
+
619
+ def test_delete_events_requires_both_time_bounds() -> None:
620
+ with pytest.raises(ValueError, match='start_time and end_time'):
621
+ EventPublisher(model_id=MODEL_ID).delete_events(
622
+ start_time='2025-11-17T01:00:00.000Z'
623
+ )
624
+
625
+
626
+ def test_delete_events_empty_event_ids_alone_is_no_filter() -> None:
627
+ # An empty list with no time range collapses to "no filter", which the
628
+ # refuse-to-delete-all guard rejects.
629
+ with pytest.raises(ValueError, match='Refusing to delete all events'):
630
+ EventPublisher(model_id=MODEL_ID).delete_events(event_ids=[])
631
+
632
+
633
+ def test_delete_events_rejects_over_limit_event_ids() -> None:
634
+ with pytest.raises(ValueError, match='at most 10,000'):
635
+ EventPublisher(model_id=MODEL_ID).delete_events(
636
+ event_ids=[f'id_{i}' for i in range(10_001)]
637
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiddler-client
3
- Version: 3.12.0rc1
3
+ Version: 3.13.0.dev1
4
4
  Summary: Python client for Fiddler Platform
5
5
  Author: Fiddler Labs
6
6
  License-Expression: Apache-2.0
@@ -1 +0,0 @@
1
- 3.12.0rc1
@@ -1,15 +0,0 @@
1
- from typing import Any, Dict, List
2
- from uuid import UUID
3
-
4
- from fiddler.constants.events import PublishEventsSourceType
5
- from fiddler.schemas.base import BaseModel
6
-
7
-
8
- class FileSource(BaseModel):
9
- file_id: UUID
10
- type: PublishEventsSourceType = PublishEventsSourceType.FILE
11
-
12
-
13
- class EventsSource(BaseModel):
14
- events: List[Dict[Any, Any]]
15
- type: PublishEventsSourceType = PublishEventsSourceType.EVENTS