fiddler-client 3.5.0.dev2__tar.gz → 3.6.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.
- {fiddler_client-3.5.0.dev2/fiddler_client.egg-info → fiddler_client-3.6.0.dev1}/PKG-INFO +1 -1
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/README.md +2 -2
- fiddler_client-3.6.0.dev1/fiddler/VERSION +1 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/__init__.py +0 -2
- fiddler_client-3.6.0.dev1/fiddler/configs.py +13 -0
- fiddler_client-3.6.0.dev1/fiddler/decorators.py +84 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/alert_rule.py +1 -1
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/job.py +46 -11
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/xai.py +19 -81
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/exceptions.py +14 -3
- fiddler_client-3.6.0.dev1/fiddler/libs/aws.py +95 -0
- fiddler_client-3.6.0.dev1/fiddler/libs/http_client.py +420 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/packtools/project_attributions_helpers.py +10 -8
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/xai.py +0 -6
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_alert_rule.py +3 -12
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_xai.py +0 -108
- fiddler_client-3.6.0.dev1/fiddler/tests/test_sagemaker_auth.py +75 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/utils/logger.py +7 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1/fiddler_client.egg-info}/PKG-INFO +1 -1
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler_client.egg-info/SOURCES.txt +2 -0
- fiddler_client-3.5.0.dev2/fiddler/VERSION +0 -1
- fiddler_client-3.5.0.dev2/fiddler/configs.py +0 -8
- fiddler_client-3.5.0.dev2/fiddler/decorators.py +0 -52
- fiddler_client-3.5.0.dev2/fiddler/libs/http_client.py +0 -209
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/LICENSE.txt +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/MANIFEST.in +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/PUBLIC.md +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/connection.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/alert_rule.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/baseline.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/common.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/dataset.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/events.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/job.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/model.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/model_deployment.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/constants/xai.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/alert_record.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/base.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/baseline.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/custom_expression.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/dataset.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/events.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/file.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/model.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/model_artifact.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/model_deployment.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/organization.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/project.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/surrogate.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/user.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/entities/webhook.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/libs/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/libs/json_encoder.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/libs/semver.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/packtools/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/packtools/gem.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/packtools/keras_ig_helpers.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/packtools/template_model.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/alert_record.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/alert_rule.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/base.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/baseline.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/custom_expression.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/custom_features.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/dataset.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/events.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/file.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/filter_query.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/job.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/model.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/model_deployment.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/model_schema.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/model_spec.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/model_task_params.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/organization.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/project.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/response.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/server_info.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/user.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/webhook.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/schemas/xai_params.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_alert_record.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_baseline.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_custom_metric.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_dataset.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_events.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_files.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_generate_model.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_job.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_mixin.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_model.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_model_artifact.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_model_deployment.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_model_surrogate.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_project.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_segment.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/apis/test_webhook.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/conftest.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/constants.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/test_connection.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/test_json_encoder.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/test_logger.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/test_utils.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/tests/utils.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/utils/__init__.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/utils/decorators.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/utils/helpers.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/utils/model_generator.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/utils/validations.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/utils/version.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler/version.py +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler_client.egg-info/dependency_links.txt +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler_client.egg-info/requires.txt +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/fiddler_client.egg-info/top_level.txt +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/setup.cfg +0 -0
- {fiddler_client-3.5.0.dev2 → fiddler_client-3.6.0.dev1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# fiddler-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Python library for interacting with the Fiddler HTTP API.
|
|
4
4
|
|
|
5
5
|
## Pre Release
|
|
6
6
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.6.0.dev1
|
|
@@ -57,7 +57,6 @@ from fiddler.schemas.xai import ( # noqa
|
|
|
57
57
|
DatasetDataSource,
|
|
58
58
|
EventIdDataSource,
|
|
59
59
|
RowDataSource,
|
|
60
|
-
SqlSliceQueryDataSource,
|
|
61
60
|
)
|
|
62
61
|
from fiddler.schemas.xai_params import XaiParams # noqa
|
|
63
62
|
from fiddler.utils.helpers import group_by # noqa
|
|
@@ -110,7 +109,6 @@ __all__ = [
|
|
|
110
109
|
'ModelTaskParams',
|
|
111
110
|
'Multivariate',
|
|
112
111
|
'RowDataSource',
|
|
113
|
-
'SqlSliceQueryDataSource',
|
|
114
112
|
'TextEmbedding',
|
|
115
113
|
'VectorFeature',
|
|
116
114
|
'XaiParams',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DEFAULT_CONNECTION_TIMEOUT = 60 # In seconds
|
|
2
|
+
MIN_SERVER_VERSION = '24.4.0'
|
|
3
|
+
DEFAULT_PAGE_SIZE = 50
|
|
4
|
+
|
|
5
|
+
# In seconds. For example, this generates a log message every N seconds.
|
|
6
|
+
# Was once set to 3, and I think that was too frequent. If this needs to
|
|
7
|
+
# be snappier, we can grow the interval.
|
|
8
|
+
JOB_POLL_INTERVAL = 6
|
|
9
|
+
JOB_WAIT_TIMEOUT = 1800
|
|
10
|
+
|
|
11
|
+
STREAM_EVENT_LIMIT = 1000
|
|
12
|
+
DEFAULT_NUM_CLUSTERS = 5
|
|
13
|
+
DEFAULT_NUM_TAGS = 5
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
from typing import Any, Callable, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
import pydantic
|
|
7
|
+
|
|
8
|
+
from fiddler.exceptions import ApiError, Conflict, HttpError, NotFound, Unsupported
|
|
9
|
+
from fiddler.schemas.response import ErrorResponse
|
|
10
|
+
from fiddler.utils.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
_WrappedFuncType = TypeVar( # pylint: disable=invalid-name
|
|
15
|
+
'_WrappedFuncType', bound=Callable[..., Any]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ignore: fiddler/decorators.py:19:1: C901 'handle_api_error' is too complex (11)
|
|
20
|
+
def handle_api_error(func: _WrappedFuncType) -> _WrappedFuncType: # noqa: C901
|
|
21
|
+
@wraps(func)
|
|
22
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
23
|
+
try:
|
|
24
|
+
return func(*args, **kwargs)
|
|
25
|
+
except requests.JSONDecodeError as e:
|
|
26
|
+
# This often is premature use of `json()`. Calling code should
|
|
27
|
+
# first check the response for the expected status code, and only
|
|
28
|
+
# then attempt JSON-deserialization.
|
|
29
|
+
raise HttpError(message=f'Invalid JSON response - {e.doc}') # type: ignore
|
|
30
|
+
except requests.HTTPError as http_exc:
|
|
31
|
+
# Note(JP): when we're here it means we got an HTTP response.
|
|
32
|
+
# That's how this exception is documented.
|
|
33
|
+
|
|
34
|
+
status = http_exc.response.status_code
|
|
35
|
+
# The logging levels 'critical' and 'error' are generally meant to
|
|
36
|
+
# be used for terminal / fatal errors. Here, we handle many
|
|
37
|
+
# transient (retryable) errors, and it's fair to log them not as
|
|
38
|
+
# serious as actual errors. Therefore level WARNING and not ERROR.
|
|
39
|
+
# Even INFO might make sense.
|
|
40
|
+
logger.warning(
|
|
41
|
+
'%s HTTP request to %s failed with %s - %s',
|
|
42
|
+
http_exc.request.method,
|
|
43
|
+
getattr(http_exc.request, 'url', 'unknown'),
|
|
44
|
+
status,
|
|
45
|
+
# Not entire response body in case we have a megabyte response.
|
|
46
|
+
getattr(http_exc.response, 'content', 'missing')[:200],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Now, map the error at hand to an exception to be thrown towards
|
|
50
|
+
# the caller.
|
|
51
|
+
|
|
52
|
+
if status == HTTPStatus.METHOD_NOT_ALLOWED:
|
|
53
|
+
raise Unsupported()
|
|
54
|
+
|
|
55
|
+
# Did we get structured error information? This might even
|
|
56
|
+
# be wrapped in a 501 response for example.
|
|
57
|
+
try:
|
|
58
|
+
# https://github.com/fiddler-labs/fiddler/issues/9314
|
|
59
|
+
error_resp = ErrorResponse(**http_exc.response.json())
|
|
60
|
+
except pydantic.ValidationError:
|
|
61
|
+
# We couldn't turn the HTTP error response into an object
|
|
62
|
+
# satisfying model='ErrorResponse'. Now, instead of exposing
|
|
63
|
+
# this to users as a pydantic.ValidationError (which is kinda
|
|
64
|
+
# useless to them) -- just re-raise requests.HTTPError, with
|
|
65
|
+
# all its useful error detail -- it is likely to show the
|
|
66
|
+
# actual problem.
|
|
67
|
+
raise http_exc
|
|
68
|
+
except requests.JSONDecodeError:
|
|
69
|
+
# Re-raise requests.HTTPError, with all its useful error
|
|
70
|
+
# detail.
|
|
71
|
+
raise http_exc
|
|
72
|
+
|
|
73
|
+
# This code path is only reachable if we could successfully
|
|
74
|
+
# deserialize an ErrorResponse object from the HTTP response body.
|
|
75
|
+
|
|
76
|
+
if status == HTTPStatus.CONFLICT:
|
|
77
|
+
raise Conflict(error_resp.error)
|
|
78
|
+
|
|
79
|
+
if status == HTTPStatus.NOT_FOUND:
|
|
80
|
+
raise NotFound(error_resp.error)
|
|
81
|
+
|
|
82
|
+
raise ApiError(error_resp.error)
|
|
83
|
+
|
|
84
|
+
return cast(_WrappedFuncType, wrapper)
|
|
@@ -247,7 +247,7 @@ class AlertRule(
|
|
|
247
247
|
url=self._get_url(id_=self.id), data={'enable_notification': True}
|
|
248
248
|
)
|
|
249
249
|
logger.info(
|
|
250
|
-
'
|
|
250
|
+
'notifications have been enabled for alert rule with id: %s', self.id
|
|
251
251
|
)
|
|
252
252
|
|
|
253
253
|
@handle_api_error
|
|
@@ -4,6 +4,8 @@ import time
|
|
|
4
4
|
from typing import Iterator
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
+
import requests
|
|
8
|
+
|
|
7
9
|
from fiddler.configs import JOB_POLL_INTERVAL, JOB_WAIT_TIMEOUT
|
|
8
10
|
from fiddler.constants.job import JobStatus
|
|
9
11
|
from fiddler.decorators import handle_api_error
|
|
@@ -112,24 +114,57 @@ class Job(BaseEntity): # pylint: disable=too-many-instance-attributes
|
|
|
112
114
|
:return: Iterator of job objects
|
|
113
115
|
"""
|
|
114
116
|
assert self.id is not None
|
|
117
|
+
deadline = time.monotonic() + timeout
|
|
115
118
|
|
|
116
|
-
start_time = time.time()
|
|
117
119
|
while True:
|
|
120
|
+
|
|
121
|
+
if time.monotonic() > deadline:
|
|
122
|
+
raise TimeoutError(f'Deadline exceeded while watching job {self.id}')
|
|
123
|
+
|
|
118
124
|
try:
|
|
119
|
-
|
|
125
|
+
# This can raise requests.HTTPError to represent non-2xx
|
|
126
|
+
# responses.
|
|
127
|
+
response = self._client().get(
|
|
128
|
+
url=self._get_url(id_=self.id),
|
|
129
|
+
# Short-ish TCP connect timeout, to stay responsive in
|
|
130
|
+
# terms of logging. The HTTP response latency for GET
|
|
131
|
+
# /jobs/<id> is expected to be less than couple of seconds
|
|
132
|
+
# (i.e., 30 s includes lots of leeway).
|
|
133
|
+
timeout=(5, 30),
|
|
134
|
+
# Inject `retry="off"` to disable the centralized retry
|
|
135
|
+
# machinery: ask for being confronted with the details
|
|
136
|
+
# (throw exceptions my way, so I an do my own "responsive"
|
|
137
|
+
# type of retrying). Otherwise, we'd give up control.
|
|
138
|
+
retry="off",
|
|
139
|
+
)
|
|
120
140
|
self._refresh_from_response(response)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
141
|
+
|
|
142
|
+
except requests.exceptions.HTTPError as exc:
|
|
143
|
+
# Note(JP): got a non-2xx HTTP response. The main purpose of
|
|
144
|
+
# this handler is to keep going after having received a 5xx
|
|
145
|
+
# response. In this case of GETting job status even some 404s
|
|
146
|
+
# might be worth retrying. That is, it's fine to give up only
|
|
147
|
+
# after reaching the deadline even if we collect some 4xx
|
|
148
|
+
# responses along the way. Noteworthy: Receiving a 5xx response
|
|
149
|
+
# here is not an error, it's an expected scenario to be
|
|
150
|
+
# accounted for.
|
|
151
|
+
logger.info(
|
|
152
|
+
'watch: ignore unexpected response %s (URL: %s, response body prefix: %s...)',
|
|
153
|
+
exc.response,
|
|
154
|
+
exc.request.url,
|
|
155
|
+
exc.response.text[:120],
|
|
125
156
|
)
|
|
126
157
|
continue
|
|
127
158
|
|
|
128
|
-
|
|
159
|
+
except requests.exceptions.RequestException:
|
|
160
|
+
# This error is in the hierarchy _above_ `HTTPError`, and in
|
|
161
|
+
# this setup catches all errors that are _not_ a bad response:
|
|
162
|
+
# DNS error, TCP connect timeout, err during sending request,
|
|
163
|
+
# err during receiving response. Rely on the error detail to
|
|
164
|
+
# have already been logged.
|
|
165
|
+
continue
|
|
129
166
|
|
|
130
|
-
|
|
131
|
-
if (current_time - start_time) > timeout:
|
|
132
|
-
raise TimeoutError(f'Timed out while watching job {self.id}')
|
|
167
|
+
yield self
|
|
133
168
|
|
|
134
169
|
if self.status in [
|
|
135
170
|
JobStatus.SUCCESS,
|
|
@@ -153,7 +188,7 @@ class Job(BaseEntity): # pylint: disable=too-many-instance-attributes
|
|
|
153
188
|
|
|
154
189
|
for job in self.watch(interval=interval, timeout=timeout):
|
|
155
190
|
logger.info(
|
|
156
|
-
'%s:
|
|
191
|
+
'%s: %s, progress: %.1f%%',
|
|
157
192
|
log_prefix,
|
|
158
193
|
job.status,
|
|
159
194
|
job.progress,
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
from collections import namedtuple
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
|
-
from io import BytesIO
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import Any, Callable
|
|
9
8
|
from uuid import UUID
|
|
@@ -18,15 +17,10 @@ from fiddler.constants.xai import (
|
|
|
18
17
|
)
|
|
19
18
|
from fiddler.decorators import handle_api_error
|
|
20
19
|
from fiddler.entities.job import Job
|
|
20
|
+
from fiddler.exceptions import Unsupported
|
|
21
21
|
from fiddler.schemas.job import JobCompactResp
|
|
22
|
-
from fiddler.schemas.xai import
|
|
23
|
-
DatasetDataSource,
|
|
24
|
-
EventIdDataSource,
|
|
25
|
-
RowDataSource,
|
|
26
|
-
SqlSliceQueryDataSource,
|
|
27
|
-
)
|
|
22
|
+
from fiddler.schemas.xai import DatasetDataSource, EventIdDataSource, RowDataSource
|
|
28
23
|
from fiddler.utils.decorators import check_version
|
|
29
|
-
from fiddler.utils.helpers import try_series_retype
|
|
30
24
|
from fiddler.utils.logger import get_logger
|
|
31
25
|
|
|
32
26
|
logger = get_logger(__name__)
|
|
@@ -44,7 +38,7 @@ class XaiMixin:
|
|
|
44
38
|
def explain( # pylint: disable=too-many-arguments
|
|
45
39
|
self,
|
|
46
40
|
input_data_source: RowDataSource | EventIdDataSource,
|
|
47
|
-
ref_data_source: DatasetDataSource |
|
|
41
|
+
ref_data_source: DatasetDataSource | None = None,
|
|
48
42
|
method: ExplainMethod | str = ExplainMethod.FIDDLER_SHAP,
|
|
49
43
|
num_permutations: int | None = None,
|
|
50
44
|
ci_level: float | None = None,
|
|
@@ -55,8 +49,8 @@ class XaiMixin:
|
|
|
55
49
|
|
|
56
50
|
:param input_data_source: DataSource for the input data to compute explanation
|
|
57
51
|
on (RowDataSource, EventIdDataSource)
|
|
58
|
-
:param ref_data_source:
|
|
59
|
-
on
|
|
52
|
+
:param ref_data_source: Dataset data source for the reference data to compute explanation
|
|
53
|
+
on.
|
|
60
54
|
Only used for non-text models and the following methods:
|
|
61
55
|
'SHAP', 'FIDDLER_SHAP', 'PERMUTE', 'MEAN_RESET'
|
|
62
56
|
:param method: Explanation method name. Could be your custom
|
|
@@ -102,7 +96,7 @@ class XaiMixin:
|
|
|
102
96
|
sample: bool = False,
|
|
103
97
|
max_rows: int | None = None,
|
|
104
98
|
columns: list[str] | None = None,
|
|
105
|
-
) ->
|
|
99
|
+
) -> None:
|
|
106
100
|
"""
|
|
107
101
|
Fetch data with slice query.
|
|
108
102
|
|
|
@@ -111,35 +105,12 @@ class XaiMixin:
|
|
|
111
105
|
columns to select overriding columns selected in the query.
|
|
112
106
|
:param max_rows: Number of maximum rows to fetch
|
|
113
107
|
:param sample: Whether rows should be sample or not from the database
|
|
114
|
-
|
|
115
108
|
:return: Dataframe of the query output
|
|
116
109
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
payload: dict[str, Any] = {
|
|
120
|
-
'model_id': self.id,
|
|
121
|
-
'query': query,
|
|
122
|
-
'sample': sample,
|
|
123
|
-
}
|
|
124
|
-
if max_rows:
|
|
125
|
-
payload['max_rows'] = max_rows
|
|
126
|
-
if columns:
|
|
127
|
-
payload['columns'] = columns
|
|
128
|
-
|
|
129
|
-
response = self._client().post(
|
|
130
|
-
url='/v3/slice-query/fetch',
|
|
131
|
-
data=payload,
|
|
110
|
+
raise Unsupported(
|
|
111
|
+
'This method is not supported since version 3.4. Please use `model.download_data` instead.'
|
|
132
112
|
)
|
|
133
113
|
|
|
134
|
-
response_dict = response.json()['data']
|
|
135
|
-
|
|
136
|
-
column_names = response_dict['metadata']['columns']
|
|
137
|
-
dtype_strings = response_dict['metadata']['dtypes']
|
|
138
|
-
df = pd.DataFrame(response_dict['rows'], columns=column_names)
|
|
139
|
-
for column_name, dtype in zip(column_names, dtype_strings):
|
|
140
|
-
df[column_name] = try_series_retype(df[column_name], dtype)
|
|
141
|
-
return df
|
|
142
|
-
|
|
143
114
|
@handle_api_error
|
|
144
115
|
def download_data( # pylint: disable=too-many-arguments
|
|
145
116
|
self,
|
|
@@ -250,25 +221,9 @@ class XaiMixin:
|
|
|
250
221
|
:param max_rows: Number of maximum rows to fetch
|
|
251
222
|
:param sample: Whether rows should be sample or not from the database
|
|
252
223
|
"""
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
os.makedirs(output_dir)
|
|
257
|
-
payload: dict[str, Any] = {
|
|
258
|
-
'model_id': self.id,
|
|
259
|
-
'query': query,
|
|
260
|
-
'sample': sample,
|
|
261
|
-
}
|
|
262
|
-
if max_rows:
|
|
263
|
-
payload['max_rows'] = max_rows
|
|
264
|
-
if columns:
|
|
265
|
-
payload['columns'] = columns
|
|
266
|
-
|
|
267
|
-
file_path = os.path.join(output_dir, 'output.parquet')
|
|
268
|
-
with self._client().post(url='/v3/slice-query/download', data=payload) as resp:
|
|
269
|
-
# Download parquet file
|
|
270
|
-
df = pd.read_parquet(BytesIO(resp.content))
|
|
271
|
-
df.to_parquet(file_path)
|
|
224
|
+
raise Unsupported(
|
|
225
|
+
'This method is not supported since version 3.4. Please use `model.download_data` instead.'
|
|
226
|
+
)
|
|
272
227
|
|
|
273
228
|
@handle_api_error
|
|
274
229
|
def get_mutual_info(
|
|
@@ -277,7 +232,7 @@ class XaiMixin:
|
|
|
277
232
|
column_name: str,
|
|
278
233
|
num_samples: int | None = None,
|
|
279
234
|
normalized: bool = False,
|
|
280
|
-
) ->
|
|
235
|
+
) -> None:
|
|
281
236
|
"""
|
|
282
237
|
Get mutual information.
|
|
283
238
|
|
|
@@ -290,27 +245,10 @@ class XaiMixin:
|
|
|
290
245
|
all the variables in the dataset.
|
|
291
246
|
:param num_samples: Number of samples to select for computation
|
|
292
247
|
:param normalized: If set to True, it will compute Normalized Mutual Information
|
|
293
|
-
|
|
294
248
|
:return: a dictionary of mutual information w.r.t the given feature
|
|
295
249
|
for each column given
|
|
296
250
|
"""
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
payload: dict[str, Any] = {
|
|
300
|
-
'model_id': self.id,
|
|
301
|
-
'query': query,
|
|
302
|
-
'column_name': column_name,
|
|
303
|
-
'normalized': normalized,
|
|
304
|
-
}
|
|
305
|
-
if num_samples:
|
|
306
|
-
payload['num_samples'] = num_samples
|
|
307
|
-
|
|
308
|
-
response = self._client().post(
|
|
309
|
-
url='/v3/analytics/mutual-info',
|
|
310
|
-
data=payload,
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
return response.json()['data']
|
|
251
|
+
raise Unsupported('This method is not supported since version 3.4.')
|
|
314
252
|
|
|
315
253
|
@handle_api_error
|
|
316
254
|
def predict(
|
|
@@ -344,7 +282,7 @@ class XaiMixin:
|
|
|
344
282
|
@handle_api_error
|
|
345
283
|
def get_feature_impact( # pylint: disable=too-many-arguments
|
|
346
284
|
self,
|
|
347
|
-
data_source: DatasetDataSource
|
|
285
|
+
data_source: DatasetDataSource,
|
|
348
286
|
num_iterations: int | None = None,
|
|
349
287
|
num_refs: int | None = None,
|
|
350
288
|
ci_level: float | None = None,
|
|
@@ -354,8 +292,8 @@ class XaiMixin:
|
|
|
354
292
|
"""
|
|
355
293
|
Get global feature impact for a model over a dataset or a slice.
|
|
356
294
|
|
|
357
|
-
:param data_source:
|
|
358
|
-
impact on
|
|
295
|
+
:param data_source: Dataset data Source for the input dataset to compute feature
|
|
296
|
+
impact on
|
|
359
297
|
:param num_iterations: The maximum number of ablated model inferences per feature
|
|
360
298
|
:param num_refs: The number of reference points used in the explanation
|
|
361
299
|
:param ci_level: The confidence level (between 0 and 1)
|
|
@@ -398,7 +336,7 @@ class XaiMixin:
|
|
|
398
336
|
@handle_api_error
|
|
399
337
|
def get_feature_importance( # pylint: disable=too-many-arguments
|
|
400
338
|
self,
|
|
401
|
-
data_source: DatasetDataSource
|
|
339
|
+
data_source: DatasetDataSource,
|
|
402
340
|
num_iterations: int | None = None,
|
|
403
341
|
num_refs: int | None = None,
|
|
404
342
|
ci_level: float | None = None,
|
|
@@ -406,8 +344,8 @@ class XaiMixin:
|
|
|
406
344
|
"""
|
|
407
345
|
Get global feature importance for a model over a dataset or a slice.
|
|
408
346
|
|
|
409
|
-
:param data_source:
|
|
410
|
-
importance on
|
|
347
|
+
:param data_source: Dataset data aSource for the input dataset to compute feature
|
|
348
|
+
importance on
|
|
411
349
|
:param num_iterations: The maximum number of ablated model inferences per feature
|
|
412
350
|
:param num_refs: The number of reference points used in the explanation
|
|
413
351
|
:param ci_level: The confidence level (between 0 and 1)
|
|
@@ -17,6 +17,8 @@ class BaseError(Exception):
|
|
|
17
17
|
super().__init__(self.message)
|
|
18
18
|
|
|
19
19
|
def __str__(self) -> str:
|
|
20
|
+
# It would be better to prepend context: like FiddlerBaseError. Note
|
|
21
|
+
# that this here is the string representation of an exception.
|
|
20
22
|
return str(self.message)
|
|
21
23
|
|
|
22
24
|
@property
|
|
@@ -53,17 +55,26 @@ class Unsupported(BaseError):
|
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class HttpError(BaseError):
|
|
56
|
-
"""Base class for all HTTP errors
|
|
58
|
+
"""Base class for all HTTP errors
|
|
59
|
+
|
|
60
|
+
Deprecated. Not thrown anymore.
|
|
61
|
+
"""
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
class ConnTimeout(HttpError):
|
|
60
|
-
"""Connection timeout error
|
|
65
|
+
"""Connection timeout error
|
|
66
|
+
|
|
67
|
+
Deprecated. Not thrown anymore.
|
|
68
|
+
"""
|
|
61
69
|
|
|
62
70
|
message = 'Request timed out while trying to reach endpoint'
|
|
63
71
|
|
|
64
72
|
|
|
65
73
|
class ConnError(HttpError):
|
|
66
|
-
"""Connection error
|
|
74
|
+
"""Connection error
|
|
75
|
+
|
|
76
|
+
Deprecated. Not thrown anymore.
|
|
77
|
+
"""
|
|
67
78
|
|
|
68
79
|
message = 'Unable to reach the given endpoint'
|
|
69
80
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# type: ignore
|
|
2
|
+
# TODO: do type-checking soon: https://github.com/fiddler-anyprem/hadron/issues/25
|
|
3
|
+
|
|
4
|
+
# Note(JP): allow for type hints using strings (so that for example
|
|
5
|
+
# "PartnerAppAuthProvider" can be used as a type hint w/o having to import the
|
|
6
|
+
# class).
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from collections import namedtuple
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from fiddler.exceptions import BaseError
|
|
14
|
+
from fiddler.utils.logger import get_logger
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
AwsSageMakerAuthConfig = namedtuple('AwsSageMakerAuthConfig', ['arn', 'url'])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _read_env_for_aws_sagemaker_or_raise() -> Optional[AwsSageMakerAuthConfig]:
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
Raise `fiddler.exceptions.BaseError` when the environment appears to be
|
|
25
|
+
misconfigured.
|
|
26
|
+
"""
|
|
27
|
+
av = os.getenv("AWS_PARTNER_APP_AUTH")
|
|
28
|
+
|
|
29
|
+
if av is None:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
if av.lower().strip() != "true":
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
# User has declared intent: activate SageMaker authentication. Require
|
|
36
|
+
# other environment variables to be set.
|
|
37
|
+
keys = ("AWS_PARTNER_APP_ARN", "AWS_PARTNER_APP_URL")
|
|
38
|
+
for k in keys:
|
|
39
|
+
errmsg = f"AWS_PARTNER_APP_AUTH is set to `{av}`, but {k} is not set"
|
|
40
|
+
if os.getenv(k) is None:
|
|
41
|
+
# Future: maybe have a ConfigError type?
|
|
42
|
+
raise BaseError(errmsg)
|
|
43
|
+
|
|
44
|
+
# Future: potentially do validation for got error messages, but be sure
|
|
45
|
+
# that the validation isn't getting in the way, _ever_.
|
|
46
|
+
|
|
47
|
+
# Remove leading and trailing whitespace: that's a safe operation, and
|
|
48
|
+
# always a user/config "error". That we can correct for.
|
|
49
|
+
arn = os.getenv("AWS_PARTNER_APP_ARN").strip() # type: ignore[union-attr]
|
|
50
|
+
url = os.getenv("AWS_PARTNER_APP_URL").strip() # type: ignore[union-attr]
|
|
51
|
+
return AwsSageMakerAuthConfig(arn, url)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _import_sm_sdk(c: AwsSageMakerAuthConfig): # -> "PartnerAppAuthProvider":
|
|
55
|
+
"""
|
|
56
|
+
Return `sagemaker.PartnerAppAuthProvider` instance or raise an exception.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
from sagemaker import PartnerAppAuthProvider
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
errmsg = (
|
|
62
|
+
f"The AWS_PARTNER_APP_* environment variables are set, but "
|
|
63
|
+
f"importing a dependency failed: {exc}"
|
|
64
|
+
)
|
|
65
|
+
raise BaseError(errmsg)
|
|
66
|
+
|
|
67
|
+
# Note(JP): can initialization of this auth provider object fail? Does it
|
|
68
|
+
# use Internet resources? Update 1: this call is expected to fail with e.g.
|
|
69
|
+
# `ValueError: Must specify a valid AWS_PARTNER_APP_ARN environment
|
|
70
|
+
# variable` when this env var is not set. Update 2: this does indeed deeper
|
|
71
|
+
# validation of the user-given config values. May throw for example
|
|
72
|
+
# "ValueError: Must specify a valid AWS_PARTNER_APP_ARN environment
|
|
73
|
+
# variable" when something non-ARN-looking has been provided. That is, we
|
|
74
|
+
# can (and should) skip validating these env var values and lave this job
|
|
75
|
+
# to the SM SDK.
|
|
76
|
+
p = PartnerAppAuthProvider()
|
|
77
|
+
logger.info("initialized AWS SageMaker authentication provider: %s", p)
|
|
78
|
+
return p
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def conditionally_init_aws_sm_auth(): # -> Optional["PartnerAppAuthProvider"]:
|
|
82
|
+
"""
|
|
83
|
+
Return `None` or `PartnerAppAuthProvider` instance.
|
|
84
|
+
|
|
85
|
+
Raise an exception upon configuration error or when dependencies are
|
|
86
|
+
missing.
|
|
87
|
+
"""
|
|
88
|
+
aws_sm_auth_cfg = _read_env_for_aws_sagemaker_or_raise()
|
|
89
|
+
|
|
90
|
+
logger.debug("auth config: %s", aws_sm_auth_cfg)
|
|
91
|
+
if aws_sm_auth_cfg is not None:
|
|
92
|
+
return _import_sm_sdk(aws_sm_auth_cfg)
|
|
93
|
+
|
|
94
|
+
# Just to make this expected case explicit.
|
|
95
|
+
return None
|