acceldata-sdk-python 26.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acceldata/__init__.py +7 -0
- acceldata/client/__init__.py +0 -0
- acceldata/client/adoc_client.py +666 -0
- acceldata/client/http_client.py +346 -0
- acceldata/client/transient_retry.py +142 -0
- acceldata/constants/__init__.py +1 -0
- acceldata/constants/constants.py +13 -0
- acceldata/exceptions/__init__.py +52 -0
- acceldata/exceptions/api_errors.py +85 -0
- acceldata/exceptions/exceptions.py +174 -0
- acceldata/exceptions/sdk_exceptions.py +98 -0
- acceldata/mappers/__init__.py +21 -0
- acceldata/mappers/catalog_executions.py +107 -0
- acceldata/models/__init__.py +0 -0
- acceldata/models/api/catalog/above_configs.py +99 -0
- acceldata/models/api/catalog/above_configs_success.py +138 -0
- acceldata/models/api/catalog/absolute_configs.py +197 -0
- acceldata/models/api/catalog/absolute_configs_fallback.py +101 -0
- acceldata/models/api/catalog/absolute_threshold_config.py +105 -0
- acceldata/models/api/catalog/analysis_job_context.py +102 -0
- acceldata/models/api/catalog/analytics_pipeline.py +385 -0
- acceldata/models/api/catalog/analytics_pipeline_endpoint.py +136 -0
- acceldata/models/api/catalog/anomaly_config.py +95 -0
- acceldata/models/api/catalog/anomaly_details.py +119 -0
- acceldata/models/api/catalog/anomaly_threshold_config.py +105 -0
- acceldata/models/api/catalog/artifact.py +92 -0
- acceldata/models/api/catalog/assembly.py +171 -0
- acceldata/models/api/catalog/asset.py +162 -0
- acceldata/models/api/catalog/asset_catalog_listing.py +159 -0
- acceldata/models/api/catalog/asset_catalog_listing_response.py +102 -0
- acceldata/models/api/catalog/asset_label_request.py +96 -0
- acceldata/models/api/catalog/asset_progress.py +96 -0
- acceldata/models/api/catalog/asset_progress_details.py +117 -0
- acceldata/models/api/catalog/asset_tag.py +124 -0
- acceldata/models/api/catalog/asset_tag_info.py +140 -0
- acceldata/models/api/catalog/asset_tag_request.py +95 -0
- acceldata/models/api/catalog/asset_tag_response.py +96 -0
- acceldata/models/api/catalog/asset_tags_response.py +102 -0
- acceldata/models/api/catalog/asset_type.py +96 -0
- acceldata/models/api/catalog/auto_anomaly_feature_analysis.py +160 -0
- acceldata/models/api/catalog/auto_anomaly_item_execution.py +133 -0
- acceldata/models/api/catalog/auto_anomaly_meta_data.py +162 -0
- acceldata/models/api/catalog/auto_anomaly_rule_execution.py +102 -0
- acceldata/models/api/catalog/auto_anomaly_summary.py +124 -0
- acceldata/models/api/catalog/backing_asset.py +126 -0
- acceldata/models/api/catalog/below_configs.py +99 -0
- acceldata/models/api/catalog/big_decimal.py +100 -0
- acceldata/models/api/catalog/big_integer.py +98 -0
- acceldata/models/api/catalog/bounds_date_time_marker_config.py +110 -0
- acceldata/models/api/catalog/bounds_file_event_marker_config.py +108 -0
- acceldata/models/api/catalog/bounds_id_marker_config.py +96 -0
- acceldata/models/api/catalog/catalog_asset_meta_data.py +128 -0
- acceldata/models/api/catalog/changes_by_config.py +105 -0
- acceldata/models/api/catalog/column_mapping.py +139 -0
- acceldata/models/api/catalog/column_metadata.py +90 -0
- acceldata/models/api/catalog/column_metric_with_anomaly_details.py +119 -0
- acceldata/models/api/catalog/column_variable.py +111 -0
- acceldata/models/api/catalog/composite_policy_execution.py +102 -0
- acceldata/models/api/catalog/composite_policy_item_execution.py +104 -0
- acceldata/models/api/catalog/connection.py +118 -0
- acceldata/models/api/catalog/connection_test_result.py +106 -0
- acceldata/models/api/catalog/connection_type.py +90 -0
- acceldata/models/api/catalog/connection_types_response.py +96 -0
- acceldata/models/api/catalog/crawler.py +88 -0
- acceldata/models/api/catalog/crawler_action_response.py +90 -0
- acceldata/models/api/catalog/crawler_execution.py +147 -0
- acceldata/models/api/catalog/crawler_execution_response.py +102 -0
- acceldata/models/api/catalog/crawler_status_response.py +90 -0
- acceldata/models/api/catalog/create_profile.py +194 -0
- acceldata/models/api/catalog/custom_sql_config.py +121 -0
- acceldata/models/api/catalog/data_bricks_spark_resource_config.py +104 -0
- acceldata/models/api/catalog/data_cadence_rule.py +236 -0
- acceldata/models/api/catalog/data_cadence_rule_details.py +125 -0
- acceldata/models/api/catalog/data_cadence_rule_execution.py +102 -0
- acceldata/models/api/catalog/data_cadence_rule_item.py +104 -0
- acceldata/models/api/catalog/data_cadence_rule_item_execution.py +158 -0
- acceldata/models/api/catalog/data_cadence_rule_response.py +98 -0
- acceldata/models/api/catalog/data_drift_execution.py +102 -0
- acceldata/models/api/catalog/data_drift_item_execution.py +121 -0
- acceldata/models/api/catalog/data_quality_engine.py +90 -0
- acceldata/models/api/catalog/data_quality_execution.py +106 -0
- acceldata/models/api/catalog/data_quality_rule.py +323 -0
- acceldata/models/api/catalog/data_quality_rule_details.py +186 -0
- acceldata/models/api/catalog/data_quality_rule_response.py +120 -0
- acceldata/models/api/catalog/data_quality_rules_response.py +102 -0
- acceldata/models/api/catalog/databricks_compute_config.py +113 -0
- acceldata/models/api/catalog/dataplane_engine.py +105 -0
- acceldata/models/api/catalog/dataplane_health_metrics.py +101 -0
- acceldata/models/api/catalog/dataplane_job.py +216 -0
- acceldata/models/api/catalog/dataplane_job_status.py +113 -0
- acceldata/models/api/catalog/dataplane_request.py +96 -0
- acceldata/models/api/catalog/dataproc_compute_config.py +106 -0
- acceldata/models/api/catalog/dataproc_spark_resource_config.py +104 -0
- acceldata/models/api/catalog/date_based_partition_marker_config.py +116 -0
- acceldata/models/api/catalog/date_partitioning_marker_config.py +113 -0
- acceldata/models/api/catalog/date_time_marker_config.py +109 -0
- acceldata/models/api/catalog/day_month_year_based_partition_marker_config.py +124 -0
- acceldata/models/api/catalog/decreased_by_config.py +105 -0
- acceldata/models/api/catalog/delete_artifact_response.py +88 -0
- acceldata/models/api/catalog/dq_rule_execution.py +248 -0
- acceldata/models/api/catalog/drift.py +121 -0
- acceldata/models/api/catalog/enable_rule_request.py +88 -0
- acceldata/models/api/catalog/engine.py +90 -0
- acceldata/models/api/catalog/execution_cancellation_response.py +92 -0
- acceldata/models/api/catalog/execution_response.py +144 -0
- acceldata/models/api/catalog/execution_result.py +126 -0
- acceldata/models/api/catalog/executions_response.py +102 -0
- acceldata/models/api/catalog/failed_asset_detail.py +90 -0
- acceldata/models/api/catalog/failed_summary.py +98 -0
- acceldata/models/api/catalog/file.py +94 -0
- acceldata/models/api/catalog/file_marker_config.py +99 -0
- acceldata/models/api/catalog/generic_config_fallback.py +101 -0
- acceldata/models/api/catalog/generic_execution.py +221 -0
- acceldata/models/api/catalog/generic_rule_execution.py +137 -0
- acceldata/models/api/catalog/global_storage_config_dto.py +172 -0
- acceldata/models/api/catalog/hosted_dp_deployment_response.py +98 -0
- acceldata/models/api/catalog/id_marker_config.py +94 -0
- acceldata/models/api/catalog/increased_by_config.py +105 -0
- acceldata/models/api/catalog/item.py +160 -0
- acceldata/models/api/catalog/item_execution.py +192 -0
- acceldata/models/api/catalog/item_segment.py +131 -0
- acceldata/models/api/catalog/job_schedule.py +118 -0
- acceldata/models/api/catalog/job_spec.py +96 -0
- acceldata/models/api/catalog/kubernetes_metrics.py +91 -0
- acceldata/models/api/catalog/label.py +97 -0
- acceldata/models/api/catalog/livy_compute_config.py +103 -0
- acceldata/models/api/catalog/livy_spark_resource_config.py +106 -0
- acceldata/models/api/catalog/look_back_config.py +90 -0
- acceldata/models/api/catalog/manual_tag_request.py +92 -0
- acceldata/models/api/catalog/mapping.py +97 -0
- acceldata/models/api/catalog/marker.py +88 -0
- acceldata/models/api/catalog/marker_config.py +349 -0
- acceldata/models/api/catalog/marker_config_fallback.py +101 -0
- acceldata/models/api/catalog/meta.py +92 -0
- acceldata/models/api/catalog/meta_data.py +92 -0
- acceldata/models/api/catalog/meta_data_item.py +94 -0
- acceldata/models/api/catalog/mini_profile_request.py +112 -0
- acceldata/models/api/catalog/notification_payload.py +114 -0
- acceldata/models/api/catalog/offset_based_marker_config.py +97 -0
- acceldata/models/api/catalog/operational_config.py +92 -0
- acceldata/models/api/catalog/pattern_configuration.py +90 -0
- acceldata/models/api/catalog/pipe_line_request.py +92 -0
- acceldata/models/api/catalog/pipeline_compute_config.py +110 -0
- acceldata/models/api/catalog/policy_execution_marker_config.py +94 -0
- acceldata/models/api/catalog/policy_execution_request.py +229 -0
- acceldata/models/api/catalog/policy_group.py +114 -0
- acceldata/models/api/catalog/policy_transform_udf.py +113 -0
- acceldata/models/api/catalog/process_metrics.py +91 -0
- acceldata/models/api/catalog/profile_anomaly_details_response.py +109 -0
- acceldata/models/api/catalog/profile_anomaly_item_execution.py +127 -0
- acceldata/models/api/catalog/profile_anomaly_rule_execution.py +102 -0
- acceldata/models/api/catalog/profile_anomaly_rule_item.py +109 -0
- acceldata/models/api/catalog/profile_cancellation_response.py +92 -0
- acceldata/models/api/catalog/profile_request.py +211 -0
- acceldata/models/api/catalog/profile_request_response.py +92 -0
- acceldata/models/api/catalog/property_template.py +178 -0
- acceldata/models/api/catalog/property_templates.py +123 -0
- acceldata/models/api/catalog/pub_sub_lookback_marker_config.py +96 -0
- acceldata/models/api/catalog/pub_sub_timestamp_marker_config.py +108 -0
- acceldata/models/api/catalog/quality_rule.py +381 -0
- acceldata/models/api/catalog/quality_rule_execution_metrics.py +114 -0
- acceldata/models/api/catalog/quality_rule_with_latest_execution.py +115 -0
- acceldata/models/api/catalog/range_configs.py +109 -0
- acceldata/models/api/catalog/reconciliation_item.py +102 -0
- acceldata/models/api/catalog/reconciliation_item_execution.py +157 -0
- acceldata/models/api/catalog/reconciliation_rule.py +339 -0
- acceldata/models/api/catalog/reconciliation_rule_details.py +225 -0
- acceldata/models/api/catalog/reconciliation_rule_execution.py +102 -0
- acceldata/models/api/catalog/reconciliation_rule_response.py +98 -0
- acceldata/models/api/catalog/reconciliation_rules_response.py +102 -0
- acceldata/models/api/catalog/relative_config.py +197 -0
- acceldata/models/api/catalog/relative_config_fallback.py +101 -0
- acceldata/models/api/catalog/relative_threshold_config.py +111 -0
- acceldata/models/api/catalog/root_cause_analysis.py +108 -0
- acceldata/models/api/catalog/rule_execution.py +327 -0
- acceldata/models/api/catalog/rule_execution_result.py +90 -0
- acceldata/models/api/catalog/rule_item_result.py +125 -0
- acceldata/models/api/catalog/rule_meta_data.py +94 -0
- acceldata/models/api/catalog/rule_spark_sql_dynamic_filter_variable_mapping.py +100 -0
- acceldata/models/api/catalog/rule_tag.py +101 -0
- acceldata/models/api/catalog/rule_threshold_configuration.py +236 -0
- acceldata/models/api/catalog/rule_threshold_configuration_fallback.py +101 -0
- acceldata/models/api/catalog/rules_with_latest_execution_response.py +102 -0
- acceldata/models/api/catalog/sample_data_api_response.py +122 -0
- acceldata/models/api/catalog/schema_drift_item_execution.py +131 -0
- acceldata/models/api/catalog/schema_drift_rule_execution.py +102 -0
- acceldata/models/api/catalog/secret_manager_configuration.py +90 -0
- acceldata/models/api/catalog/secret_manager_configuration_with_option.py +98 -0
- acceldata/models/api/catalog/security_config.py +90 -0
- acceldata/models/api/catalog/segment_metric_json.py +142 -0
- acceldata/models/api/catalog/segments_analysis.py +145 -0
- acceldata/models/api/catalog/short_segment.py +97 -0
- acceldata/models/api/catalog/simple_quality_rule_response.py +92 -0
- acceldata/models/api/catalog/snapshot_data.py +117 -0
- acceldata/models/api/catalog/source_model.py +90 -0
- acceldata/models/api/catalog/source_type.py +100 -0
- acceldata/models/api/catalog/source_types_response.py +102 -0
- acceldata/models/api/catalog/spark_job_status.py +113 -0
- acceldata/models/api/catalog/spark_resource_config.py +114 -0
- acceldata/models/api/catalog/standard_response_assembly.py +92 -0
- acceldata/models/api/catalog/standard_response_crawler_action_response.py +92 -0
- acceldata/models/api/catalog/standard_response_crawler_status_response.py +92 -0
- acceldata/models/api/catalog/standard_response_delete_artifact_response.py +92 -0
- acceldata/models/api/catalog/standard_response_list.py +96 -0
- acceldata/models/api/catalog/standard_response_map.py +88 -0
- acceldata/models/api/catalog/standard_response_meta_data.py +92 -0
- acceldata/models/api/catalog/standard_response_mini_profile_request.py +92 -0
- acceldata/models/api/catalog/standard_response_profile_request.py +92 -0
- acceldata/models/api/catalog/start_profiling_request.py +92 -0
- acceldata/models/api/catalog/tag_domain.py +116 -0
- acceldata/models/api/catalog/threshold_breach_details.py +101 -0
- acceldata/models/api/catalog/time_based_config.py +92 -0
- acceldata/models/api/catalog/time_based_threshold_config.py +105 -0
- acceldata/models/api/catalog/timestamp_based_marker_config.py +115 -0
- acceldata/models/api/catalog/udf_variable.py +97 -0
- acceldata/models/api/catalog/yunikorn_compute_config.py +133 -0
- acceldata/models/api/catalog/yunikorn_spark_resource_config.py +154 -0
- acceldata/models/api/pipeline/big_decimal.py +100 -0
- acceldata/models/api/pipeline/big_integer.py +98 -0
- acceldata/models/api/pipeline/child_span_response.py +96 -0
- acceldata/models/api/pipeline/create_span_event.py +109 -0
- acceldata/models/api/pipeline/edge.py +108 -0
- acceldata/models/api/pipeline/event_alert_object.py +95 -0
- acceldata/models/api/pipeline/generic_config_fallback.py +101 -0
- acceldata/models/api/pipeline/identifier.py +146 -0
- acceldata/models/api/pipeline/job_request.py +130 -0
- acceldata/models/api/pipeline/meta.py +107 -0
- acceldata/models/api/pipeline/meta_data.py +92 -0
- acceldata/models/api/pipeline/metric.py +103 -0
- acceldata/models/api/pipeline/node.py +201 -0
- acceldata/models/api/pipeline/node_response.py +92 -0
- acceldata/models/api/pipeline/notification_payload.py +114 -0
- acceldata/models/api/pipeline/pipeline.py +152 -0
- acceldata/models/api/pipeline/pipeline_baseline_metric.py +92 -0
- acceldata/models/api/pipeline/pipeline_details.py +118 -0
- acceldata/models/api/pipeline/pipeline_details_response.py +102 -0
- acceldata/models/api/pipeline/pipeline_input_request.py +92 -0
- acceldata/models/api/pipeline/pipeline_listing_info.py +118 -0
- acceldata/models/api/pipeline/pipeline_response.py +92 -0
- acceldata/models/api/pipeline/pipeline_run.py +136 -0
- acceldata/models/api/pipeline/pipeline_run_request.py +92 -0
- acceldata/models/api/pipeline/pipeline_runs_response.py +102 -0
- acceldata/models/api/pipeline/pipeline_summary.py +98 -0
- acceldata/models/api/pipeline/pipelines_listing_response.py +102 -0
- acceldata/models/api/pipeline/policy_summary.py +102 -0
- acceldata/models/api/pipeline/span.py +115 -0
- acceldata/models/api/pipeline/span_event.py +114 -0
- acceldata/models/api/pipeline/span_event_request.py +92 -0
- acceldata/models/api/pipeline/span_event_response.py +92 -0
- acceldata/models/api/pipeline/span_request.py +94 -0
- acceldata/models/api/pipeline/spans_response.py +96 -0
- acceldata/models/api/pipeline/tag.py +97 -0
- acceldata/models/sdk/__init__.py +0 -0
- acceldata/models/sdk/catalog/__init__.py +105 -0
- acceldata/models/sdk/catalog/asset_resource.py +112 -0
- acceldata/models/sdk/catalog/asset_source_type.py +59 -0
- acceldata/models/sdk/catalog/custom_asset_metadata.py +11 -0
- acceldata/models/sdk/catalog/datasource_resource.py +88 -0
- acceldata/models/sdk/catalog/executor.py +524 -0
- acceldata/models/sdk/catalog/policy_execution_request.py +114 -0
- acceldata/models/sdk/catalog/policy_execution_type.py +16 -0
- acceldata/models/sdk/catalog/policy_filter.py +94 -0
- acceldata/models/sdk/catalog/policy_rule_resources.py +388 -0
- acceldata/models/sdk/catalog/profile_request_resource.py +77 -0
- acceldata/models/sdk/catalog/profiling.py +82 -0
- acceldata/models/sdk/catalog/profiling_configuration_error.py +5 -0
- acceldata/models/sdk/pipeline/__init__.py +62 -0
- acceldata/models/sdk/pipeline/create_job_input.py +108 -0
- acceldata/models/sdk/pipeline/create_pipeline_input_request.py +72 -0
- acceldata/models/sdk/pipeline/events/__init__.py +5 -0
- acceldata/models/sdk/pipeline/events/generic_event.py +31 -0
- acceldata/models/sdk/pipeline/events/log_event.py +34 -0
- acceldata/models/sdk/pipeline/events/span_event.py +8 -0
- acceldata/models/sdk/pipeline/pipeline_resource.py +171 -0
- acceldata/models/sdk/pipeline/pipeline_run_input.py +84 -0
- acceldata/models/sdk/pipeline/pipeline_run_resource.py +208 -0
- acceldata/models/sdk/pipeline/pipeline_source_type.py +20 -0
- acceldata/models/sdk/pipeline/span_resource.py +327 -0
- acceldata/openapi_deserialization_patches.py +161 -0
- acceldata/openapi_repr_patches.py +62 -0
- acceldata/services/__init__.py +0 -0
- acceldata/services/api_timestamp.py +31 -0
- acceldata/services/asset_service.py +651 -0
- acceldata/services/datasource_service.py +120 -0
- acceldata/services/generic_service.py +132 -0
- acceldata/services/marker_config.py +232 -0
- acceldata/services/pipeline_big_decimal_api.py +102 -0
- acceldata/services/pipeline_response_parse.py +209 -0
- acceldata/services/pipeline_service.py +494 -0
- acceldata/services/rule_service.py +620 -0
- acceldata/urls/__init__.py +1 -0
- acceldata/urls/catalog_urls.py +137 -0
- acceldata/urls/pipeline_urls.py +55 -0
- acceldata/utils/__init__.py +6 -0
- acceldata/utils/datetime_utils.py +16 -0
- acceldata/utils/operation_ack.py +22 -0
- acceldata/utils/span_helpers.py +95 -0
- acceldata/utils/time_range_utils.py +90 -0
- acceldata_sdk_python-26.7.0.dist-info/METADATA +26 -0
- acceldata_sdk_python-26.7.0.dist-info/RECORD +303 -0
- acceldata_sdk_python-26.7.0.dist-info/WHEEL +5 -0
- acceldata_sdk_python-26.7.0.dist-info/licenses/LICENSE +22 -0
- acceldata_sdk_python-26.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
from pydantic import JsonValue, ValidationError
|
|
8
|
+
import requests
|
|
9
|
+
from requests_toolbelt import MultipartEncoder
|
|
10
|
+
|
|
11
|
+
from acceldata.constants import CONNECTION_TIMEOUT_MS, READ_TIMEOUT_MS, CATALOG_API_PATH, PIPELINE_API_PATH, \
|
|
12
|
+
ADMIN_API_PATH, \
|
|
13
|
+
MS_TO_SECONDS, ADOC_BASE_PATH_SEGMENT
|
|
14
|
+
from acceldata.constants import USER_AGENT
|
|
15
|
+
from acceldata.exceptions import APIError, http_api_error_for_status
|
|
16
|
+
from acceldata.exceptions.exceptions import ApiException
|
|
17
|
+
from acceldata.urls import CatalogUrls
|
|
18
|
+
from acceldata.urls.pipeline_urls import PipelineUrls
|
|
19
|
+
from acceldata.utils.operation_ack import ack_failure
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _profile_cancel_is_noop_already_finished(message: str) -> bool:
|
|
23
|
+
"""Catalog may return 405 when the profile run is already terminal."""
|
|
24
|
+
m = (message or "").lower()
|
|
25
|
+
return "already finished" in m or "cannot be cancelled" in m
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BaseHttpClient:
|
|
29
|
+
logger = logging.getLogger("sdk")
|
|
30
|
+
logger.setLevel(logging.INFO)
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
url: str,
|
|
35
|
+
access_key: str,
|
|
36
|
+
secret_key: str,
|
|
37
|
+
connection_timeout_ms: int | None = CONNECTION_TIMEOUT_MS,
|
|
38
|
+
read_timeout_ms: int | None = READ_TIMEOUT_MS,
|
|
39
|
+
verify_ssl: bool | str = True,
|
|
40
|
+
) -> None:
|
|
41
|
+
# ensure valid timeouts (avoid None issues)
|
|
42
|
+
url = url.rstrip("/")
|
|
43
|
+
connection_timeout_ms = connection_timeout_ms or CONNECTION_TIMEOUT_MS
|
|
44
|
+
read_timeout_ms = read_timeout_ms or READ_TIMEOUT_MS
|
|
45
|
+
|
|
46
|
+
self._connection_timeout = connection_timeout_ms / MS_TO_SECONDS
|
|
47
|
+
self._read_timeout = read_timeout_ms / MS_TO_SECONDS
|
|
48
|
+
# requests `verify` accepts bool or a CA bundle path.
|
|
49
|
+
self._verify_ssl = verify_ssl
|
|
50
|
+
|
|
51
|
+
self._catalog_api_base = f"{url}{CATALOG_API_PATH}"
|
|
52
|
+
self._pipeline_api_base = f"{url}{PIPELINE_API_PATH}"
|
|
53
|
+
self._admin_api_base = f"{url}{ADMIN_API_PATH}"
|
|
54
|
+
self.catalog_urls = CatalogUrls(self._catalog_api_base)
|
|
55
|
+
self.pipeline_urls = PipelineUrls(self._pipeline_api_base)
|
|
56
|
+
|
|
57
|
+
if ADOC_BASE_PATH_SEGMENT in url:
|
|
58
|
+
loc = url.rfind(ADOC_BASE_PATH_SEGMENT)
|
|
59
|
+
self._admin_api_base = f"{url[:loc]}{ADMIN_API_PATH}"
|
|
60
|
+
else:
|
|
61
|
+
self._admin_api_base = f"{url}{ADMIN_API_PATH}"
|
|
62
|
+
|
|
63
|
+
# validate credentials
|
|
64
|
+
if not access_key or not secret_key:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
"ADOC credentials are missing. Please provide both access_key and secret_key."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# headers (instance scoped)
|
|
70
|
+
# add headers to distinguish between sdk calls
|
|
71
|
+
self._headers = {
|
|
72
|
+
"User-Agent": USER_AGENT,
|
|
73
|
+
"accessKey": access_key,
|
|
74
|
+
"secretKey": secret_key,
|
|
75
|
+
"Content-Type": "application/json",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
def now_ms(self) -> int:
|
|
79
|
+
return int(time.time() * 1000)
|
|
80
|
+
|
|
81
|
+
def _request(
|
|
82
|
+
self,
|
|
83
|
+
method: str,
|
|
84
|
+
url: str,
|
|
85
|
+
*,
|
|
86
|
+
params: Mapping[str, object] | None = None,
|
|
87
|
+
json_body: JsonValue | None = None,
|
|
88
|
+
data: object | None = None,
|
|
89
|
+
headers: Mapping[str, str] | None = None,
|
|
90
|
+
as_json: bool = True,
|
|
91
|
+
) -> JsonValue | str:
|
|
92
|
+
req_headers = dict(headers) if headers is not None else self._headers
|
|
93
|
+
|
|
94
|
+
now_ms = self.now_ms()
|
|
95
|
+
self.logger.debug(f"{method.upper()} {url}")
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = requests.request(
|
|
99
|
+
method=method,
|
|
100
|
+
url=url,
|
|
101
|
+
headers=req_headers,
|
|
102
|
+
timeout=(self._connection_timeout, self._read_timeout),
|
|
103
|
+
verify=self._verify_ssl,
|
|
104
|
+
params=params,
|
|
105
|
+
json=json_body,
|
|
106
|
+
data=data,
|
|
107
|
+
)
|
|
108
|
+
except requests.exceptions.RequestException as e:
|
|
109
|
+
ack_failure(
|
|
110
|
+
self.logger,
|
|
111
|
+
"http_request",
|
|
112
|
+
"transport_error",
|
|
113
|
+
method=method.upper(),
|
|
114
|
+
url=url,
|
|
115
|
+
error=str(e),
|
|
116
|
+
)
|
|
117
|
+
self.logger.error(f"{method.upper()} {url} failed: {str(e)}")
|
|
118
|
+
raise ApiException(
|
|
119
|
+
status=None,
|
|
120
|
+
reason=f"{method.upper()} {url} failed: {e!s}",
|
|
121
|
+
) from e
|
|
122
|
+
|
|
123
|
+
payload_for_log: object | None = json_body if json_body is not None else data
|
|
124
|
+
payload_str = (
|
|
125
|
+
json.dumps(payload_for_log) if payload_for_log is not None else None
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.logger.debug(
|
|
129
|
+
f"{method.upper()} {url} "
|
|
130
|
+
f"{f'payload={payload_str} ' if payload_str else ''}"
|
|
131
|
+
f"duration_ms={self.now_ms() - now_ms}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return self._response(response, as_json)
|
|
135
|
+
|
|
136
|
+
def _get(
|
|
137
|
+
self,
|
|
138
|
+
url: str,
|
|
139
|
+
params: Mapping[str, object] | None = None,
|
|
140
|
+
as_json: bool = True,
|
|
141
|
+
) -> JsonValue | str:
|
|
142
|
+
return self._request("get", url, params=params, as_json=as_json)
|
|
143
|
+
|
|
144
|
+
def _put(
|
|
145
|
+
self,
|
|
146
|
+
url: str,
|
|
147
|
+
payload: dict[str, JsonValue] | None = None,
|
|
148
|
+
as_json: bool = True,
|
|
149
|
+
) -> JsonValue | str:
|
|
150
|
+
return self._request("put", url, json_body=payload, as_json=as_json)
|
|
151
|
+
|
|
152
|
+
def _post(
|
|
153
|
+
self,
|
|
154
|
+
url: str,
|
|
155
|
+
payload: dict[str, JsonValue] | None = None,
|
|
156
|
+
as_json: bool = True,
|
|
157
|
+
params: Mapping[str, object] | None = None,
|
|
158
|
+
) -> JsonValue | str:
|
|
159
|
+
return self._request("post", url, params=params, json_body=payload, as_json=as_json)
|
|
160
|
+
|
|
161
|
+
def _delete(
|
|
162
|
+
self,
|
|
163
|
+
url: str,
|
|
164
|
+
params: Mapping[str, object] | None = None,
|
|
165
|
+
as_json: bool = True,
|
|
166
|
+
) -> JsonValue | str:
|
|
167
|
+
return self._request("delete", url, params=params, as_json=as_json)
|
|
168
|
+
|
|
169
|
+
def _post_form_data(
|
|
170
|
+
self,
|
|
171
|
+
url: str,
|
|
172
|
+
payload: dict[str, JsonValue] | None = None,
|
|
173
|
+
as_json: bool = True,
|
|
174
|
+
) -> JsonValue | str:
|
|
175
|
+
data = MultipartEncoder(fields={"request": json.dumps(payload)})
|
|
176
|
+
|
|
177
|
+
headers = dict(self._headers)
|
|
178
|
+
headers["Content-Type"] = data.content_type
|
|
179
|
+
|
|
180
|
+
return self._request(
|
|
181
|
+
"post",
|
|
182
|
+
url,
|
|
183
|
+
data=data,
|
|
184
|
+
headers=headers,
|
|
185
|
+
as_json=as_json,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _put_form_data(
|
|
189
|
+
self,
|
|
190
|
+
url: str,
|
|
191
|
+
payload: dict[str, JsonValue] | None = None,
|
|
192
|
+
as_json: bool = True,
|
|
193
|
+
) -> JsonValue | str:
|
|
194
|
+
data = MultipartEncoder(fields={"request": json.dumps(payload)})
|
|
195
|
+
|
|
196
|
+
headers = dict(self._headers)
|
|
197
|
+
headers["Content-Type"] = data.content_type
|
|
198
|
+
|
|
199
|
+
return self._request(
|
|
200
|
+
"put",
|
|
201
|
+
url,
|
|
202
|
+
data=data,
|
|
203
|
+
headers=headers,
|
|
204
|
+
as_json=as_json,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def cancel_profile(self, profile_req_id: int):
|
|
208
|
+
"""PUT ``/assets/profile/{id}/cancel`` and return a response with a user-readable message."""
|
|
209
|
+
from acceldata.models.api.catalog.profile_cancellation_response import (
|
|
210
|
+
ProfileCancellationResponse,
|
|
211
|
+
)
|
|
212
|
+
from acceldata.models.api.catalog.connection_test_result import ConnectionTestResult
|
|
213
|
+
|
|
214
|
+
url = self.catalog_urls.profile_cancel(profile_req_id)
|
|
215
|
+
try:
|
|
216
|
+
response = self._put(url, payload={})
|
|
217
|
+
except APIError as e:
|
|
218
|
+
# Latest-status can race with completion; catalog rejects cancel with 405.
|
|
219
|
+
if e.status_code == 405 and _profile_cancel_is_noop_already_finished(e.message):
|
|
220
|
+
self.logger.debug(
|
|
221
|
+
"cancel_profile(%s): skipping no-op cancel — %s",
|
|
222
|
+
profile_req_id,
|
|
223
|
+
e.message,
|
|
224
|
+
)
|
|
225
|
+
return ProfileCancellationResponse(
|
|
226
|
+
result=ConnectionTestResult(
|
|
227
|
+
isParentAvailable=True,
|
|
228
|
+
parentList={},
|
|
229
|
+
requestId=str(profile_req_id),
|
|
230
|
+
status="NO_OP",
|
|
231
|
+
message=f"Profile request {profile_req_id} is already in terminal state; nothing to cancel.",
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
raise
|
|
235
|
+
if not isinstance(response, dict):
|
|
236
|
+
return ProfileCancellationResponse(
|
|
237
|
+
result=ConnectionTestResult(
|
|
238
|
+
isParentAvailable=True,
|
|
239
|
+
parentList={},
|
|
240
|
+
requestId=str(profile_req_id),
|
|
241
|
+
status="UNKNOWN",
|
|
242
|
+
message=f"Profile request {profile_req_id} cancellation was submitted but response could not be parsed.",
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
try:
|
|
246
|
+
parsed = ProfileCancellationResponse.from_dict(response)
|
|
247
|
+
if parsed is None:
|
|
248
|
+
raise ValueError("empty profile cancellation response")
|
|
249
|
+
if parsed.result.message is None:
|
|
250
|
+
parsed.result.message = (
|
|
251
|
+
f"Profile request {profile_req_id} cancellation request was accepted "
|
|
252
|
+
f"(status={parsed.result.status})."
|
|
253
|
+
)
|
|
254
|
+
if parsed.result.request_id is None:
|
|
255
|
+
parsed.result.request_id = str(profile_req_id)
|
|
256
|
+
return parsed
|
|
257
|
+
except (ValidationError, ValueError, TypeError):
|
|
258
|
+
self.logger.debug(
|
|
259
|
+
"cancel_profile(%s): response did not match ProfileCancellationResponse",
|
|
260
|
+
profile_req_id,
|
|
261
|
+
exc_info=True,
|
|
262
|
+
)
|
|
263
|
+
return ProfileCancellationResponse(
|
|
264
|
+
result=ConnectionTestResult(
|
|
265
|
+
isParentAvailable=True,
|
|
266
|
+
parentList={},
|
|
267
|
+
requestId=str(profile_req_id),
|
|
268
|
+
status="UNKNOWN",
|
|
269
|
+
message=f"Profile request {profile_req_id} cancellation was submitted but response format was unexpected.",
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def _response(self, response: requests.Response, as_json: bool) -> JsonValue | str:
|
|
274
|
+
try:
|
|
275
|
+
response.raise_for_status()
|
|
276
|
+
except requests.exceptions.HTTPError as e:
|
|
277
|
+
self._handle_http_error(response, e)
|
|
278
|
+
|
|
279
|
+
if as_json:
|
|
280
|
+
# DELETE and some other endpoints return 2xx with an empty body; avoid json() on empty content.
|
|
281
|
+
if not response.content or not response.content.strip():
|
|
282
|
+
return {}
|
|
283
|
+
return cast(JsonValue, response.json())
|
|
284
|
+
return response.text
|
|
285
|
+
|
|
286
|
+
def _handle_http_error(self, response: requests.Response, exception: Exception) -> None:
|
|
287
|
+
status_code = response.status_code
|
|
288
|
+
url = response.url
|
|
289
|
+
|
|
290
|
+
try:
|
|
291
|
+
error_body: JsonValue | str = response.json()
|
|
292
|
+
except ValueError:
|
|
293
|
+
error_body = response.text
|
|
294
|
+
|
|
295
|
+
message = self._extract_error_message(error_body)
|
|
296
|
+
ack_failure(
|
|
297
|
+
self.logger,
|
|
298
|
+
"http_request",
|
|
299
|
+
"http_error",
|
|
300
|
+
status_code=status_code,
|
|
301
|
+
url=url,
|
|
302
|
+
message=message,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# 409 conflicts are often retried by callers; avoid ERROR noise (still raises).
|
|
306
|
+
if status_code == 409:
|
|
307
|
+
self.logger.warning(
|
|
308
|
+
"HTTP %s error for %s: %s",
|
|
309
|
+
status_code,
|
|
310
|
+
url,
|
|
311
|
+
message,
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
self.logger.error(
|
|
315
|
+
"HTTP %s error for %s: %s",
|
|
316
|
+
status_code,
|
|
317
|
+
url,
|
|
318
|
+
message,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
raise http_api_error_for_status(
|
|
322
|
+
status_code=status_code,
|
|
323
|
+
message=message,
|
|
324
|
+
response=error_body,
|
|
325
|
+
url=url,
|
|
326
|
+
) from exception
|
|
327
|
+
|
|
328
|
+
def _extract_error_message(self, error_body: JsonValue | str) -> str:
|
|
329
|
+
if isinstance(error_body, dict):
|
|
330
|
+
for key in ("message", "error", "detail"):
|
|
331
|
+
v = error_body.get(key)
|
|
332
|
+
if v is not None:
|
|
333
|
+
return str(v)
|
|
334
|
+
# Catalog often returns { "errors": [ { "message": "...", "status": 422 } ] }
|
|
335
|
+
errors = error_body.get("errors")
|
|
336
|
+
if isinstance(errors, list):
|
|
337
|
+
parts: list[str] = []
|
|
338
|
+
for item in errors:
|
|
339
|
+
if isinstance(item, dict):
|
|
340
|
+
m = item.get("message")
|
|
341
|
+
if m is not None:
|
|
342
|
+
parts.append(str(m))
|
|
343
|
+
if parts:
|
|
344
|
+
return "; ".join(parts)
|
|
345
|
+
return str(error_body)
|
|
346
|
+
return str(error_body)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Transient retries for selected SDK operations.
|
|
2
|
+
|
|
3
|
+
Some calls wrap work in :func:`transient_retry_call`. When retry is active, a
|
|
4
|
+
failed attempt may be followed by further attempts instead of failing immediately.
|
|
5
|
+
|
|
6
|
+
**What gets retried**
|
|
7
|
+
|
|
8
|
+
* **HTTP (``APIError``):** only when ``status_code`` is one of the configured
|
|
9
|
+
retryable codes (default set ``_DEFAULT_RETRY_STATUS_CODES``: **408, 409, 422, 429, 500, 502, 503, 504**).
|
|
10
|
+
Responses with no status code or other codes are not retried. Pass
|
|
11
|
+
``RetryConfig(retry_status_codes=...)`` to replace or extend that set (for example
|
|
12
|
+
``frozenset(_DEFAULT_RETRY_STATUS_CODES) | {418}``).
|
|
13
|
+
* **Transport (``ApiException``):** low-level failures before a normal API
|
|
14
|
+
response (e.g. connection errors). These are retried while attempts remain.
|
|
15
|
+
|
|
16
|
+
**Default :class:`RetryConfig`**
|
|
17
|
+
|
|
18
|
+
* ``enabled=True``
|
|
19
|
+
* ``max_attempts=10`` — at most ten tries in total (first call plus up to nine retries).
|
|
20
|
+
* Backoff: wait **30s** before the 2nd attempt, then **60s**, **120s**, … doubling each
|
|
21
|
+
time, capped at **600s** (10 minutes) between attempts.
|
|
22
|
+
* ``retry_status_codes`` defaults to ``_DEFAULT_RETRY_STATUS_CODES``; pass a custom
|
|
23
|
+
``frozenset[int]`` to override which HTTP statuses count as transient.
|
|
24
|
+
|
|
25
|
+
**Disabling / no config**
|
|
26
|
+
|
|
27
|
+
* ``RetryConfig(enabled=False)`` or ``max_attempts < 1``: run once with no retries.
|
|
28
|
+
* ``config is None``: same as disabled (often when no ``RetryConfig`` is passed).
|
|
29
|
+
|
|
30
|
+
**Merging two optional configs**
|
|
31
|
+
|
|
32
|
+
:func:`effective_transient_retry(override, default)` returns ``override`` when it
|
|
33
|
+
is a ``RetryConfig`` instance; otherwise ``default`` when that is a
|
|
34
|
+
``RetryConfig``; otherwise ``None``. Policy polling uses this so per-call kwargs
|
|
35
|
+
can override an ``Executor``-level default. Crawler and profiling calls use
|
|
36
|
+
``effective_transient_retry(explicit, None)`` — only an explicit per-call
|
|
37
|
+
``RetryConfig`` enables retries there.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import logging
|
|
43
|
+
import time
|
|
44
|
+
from dataclasses import dataclass, field
|
|
45
|
+
from typing import Callable, TypeVar
|
|
46
|
+
|
|
47
|
+
from acceldata.exceptions import APIError
|
|
48
|
+
from acceldata.exceptions.exceptions import ApiException
|
|
49
|
+
|
|
50
|
+
T = TypeVar("T")
|
|
51
|
+
|
|
52
|
+
_DEFAULT_RETRY_STATUS_CODES: frozenset[int] = frozenset(
|
|
53
|
+
{408,409, 422, 429, 500, 502, 503, 504}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class RetryConfig:
|
|
59
|
+
"""Settings for :func:`transient_retry_call` (enabled, caps, backoff, HTTP codes).
|
|
60
|
+
|
|
61
|
+
``retry_status_codes`` defaults to ``_DEFAULT_RETRY_STATUS_CODES``; supply your own
|
|
62
|
+
``frozenset`` to override (merge with the default if you only need to add or remove codes).
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
enabled: bool = True
|
|
66
|
+
max_attempts: int = 10
|
|
67
|
+
initial_interval_seconds: float = 30.0
|
|
68
|
+
max_interval_seconds: float = 600.0
|
|
69
|
+
retry_status_codes: frozenset[int] = field(
|
|
70
|
+
default_factory=lambda: frozenset(_DEFAULT_RETRY_STATUS_CODES)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def sleep_after_failed_attempt(self, failed_attempt: int) -> None:
|
|
74
|
+
"""Wait before the next try. ``failed_attempt`` is 1-based (1 = first try failed)."""
|
|
75
|
+
delay = min(
|
|
76
|
+
self.initial_interval_seconds * (2 ** (failed_attempt - 1)),
|
|
77
|
+
self.max_interval_seconds,
|
|
78
|
+
)
|
|
79
|
+
time.sleep(delay)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _retry_http_error(exc: APIError, config: RetryConfig) -> bool:
|
|
83
|
+
code = exc.status_code
|
|
84
|
+
return code is not None and code in config.retry_status_codes
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _retries_active(config: RetryConfig | None) -> bool:
|
|
88
|
+
return config is not None and config.enabled and config.max_attempts >= 1
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def transient_retry_call(
|
|
92
|
+
operation: Callable[[], T],
|
|
93
|
+
config: RetryConfig | None,
|
|
94
|
+
*,
|
|
95
|
+
logger: logging.Logger,
|
|
96
|
+
operation_name: str,
|
|
97
|
+
) -> T:
|
|
98
|
+
"""Run ``operation``, optionally retrying on transient ``APIError`` / ``ApiException``."""
|
|
99
|
+
if not _retries_active(config):
|
|
100
|
+
return operation()
|
|
101
|
+
|
|
102
|
+
assert config is not None # for type checkers; _retries_active ensures this
|
|
103
|
+
|
|
104
|
+
for attempt in range(1, config.max_attempts + 1):
|
|
105
|
+
try:
|
|
106
|
+
return operation()
|
|
107
|
+
except APIError as e:
|
|
108
|
+
last = attempt >= config.max_attempts or not _retry_http_error(e, config)
|
|
109
|
+
if last:
|
|
110
|
+
raise
|
|
111
|
+
logger.warning(
|
|
112
|
+
"%s: transient HTTP error (attempt %s/%s) status=%s: %s — retrying",
|
|
113
|
+
operation_name,
|
|
114
|
+
attempt,
|
|
115
|
+
config.max_attempts,
|
|
116
|
+
e.status_code,
|
|
117
|
+
e.message,
|
|
118
|
+
)
|
|
119
|
+
config.sleep_after_failed_attempt(attempt)
|
|
120
|
+
except ApiException as e:
|
|
121
|
+
if attempt >= config.max_attempts:
|
|
122
|
+
raise
|
|
123
|
+
logger.warning(
|
|
124
|
+
"%s: transport error (attempt %s/%s): %s — retrying",
|
|
125
|
+
operation_name,
|
|
126
|
+
attempt,
|
|
127
|
+
config.max_attempts,
|
|
128
|
+
e.reason,
|
|
129
|
+
)
|
|
130
|
+
config.sleep_after_failed_attempt(attempt)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def effective_transient_retry(
|
|
134
|
+
override: RetryConfig | None,
|
|
135
|
+
default: RetryConfig | None,
|
|
136
|
+
) -> RetryConfig | None:
|
|
137
|
+
"""Prefer an explicit per-call ``override``; else use ``default`` if it is a ``RetryConfig``."""
|
|
138
|
+
if isinstance(override, RetryConfig):
|
|
139
|
+
return override
|
|
140
|
+
if isinstance(default, RetryConfig):
|
|
141
|
+
return default
|
|
142
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .constants import *
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
CONNECTION_TIMEOUT_MS = 5000
|
|
2
|
+
READ_TIMEOUT_MS = 15000
|
|
3
|
+
|
|
4
|
+
# Control-plane URL path segments (same wire paths as the deployed product).
|
|
5
|
+
_CP = bytes.fromhex("746f726368").decode("ascii")
|
|
6
|
+
CATALOG_API_PATH = "/catalog-server/api"
|
|
7
|
+
PIPELINE_API_PATH = f"/{_CP}-pipeline/api"
|
|
8
|
+
ADMIN_API_PATH = "/admin/api"
|
|
9
|
+
|
|
10
|
+
# When the base URL includes this path segment, admin API calls use the host above it.
|
|
11
|
+
ADOC_BASE_PATH_SEGMENT = f"/{_CP}"
|
|
12
|
+
MS_TO_SECONDS = 1000
|
|
13
|
+
USER_AGENT = "Acceldata-sdk"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Public SDK exceptions.
|
|
2
|
+
|
|
3
|
+
* **HTTP errors** — :mod:`acceldata.exceptions.api_errors` (subclasses of
|
|
4
|
+
:class:`~acceldata.exceptions.exceptions.OpenApiException`): raised from
|
|
5
|
+
:class:`~acceldata.client.http_client.BaseHttpClient` on non-2xx responses.
|
|
6
|
+
* **Workflow / validation** — :mod:`acceldata.exceptions.sdk_exceptions`: raised
|
|
7
|
+
for invalid SDK usage or policy/pipeline outcomes (not transport failures).
|
|
8
|
+
* **OpenAPI helpers** — :mod:`acceldata.exceptions.exceptions`: generator-owned
|
|
9
|
+
base types (e.g. :class:`~acceldata.exceptions.exceptions.ApiException` for
|
|
10
|
+
transport failures before an HTTP response).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .api_errors import (
|
|
14
|
+
APIError,
|
|
15
|
+
BadRequestError,
|
|
16
|
+
ConflictError,
|
|
17
|
+
ForbiddenError,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
ServiceError,
|
|
20
|
+
UnauthorizedError,
|
|
21
|
+
http_api_error_for_status,
|
|
22
|
+
)
|
|
23
|
+
from .exceptions import ApiException, OpenApiException
|
|
24
|
+
from .sdk_exceptions import (
|
|
25
|
+
AcceldataSdkException,
|
|
26
|
+
InvalidPipelineRunLookupError,
|
|
27
|
+
InvalidPolicyTypeError,
|
|
28
|
+
InvalidRuleTypeError,
|
|
29
|
+
PipelineRunNotFoundError,
|
|
30
|
+
PolicyExecutionCompletedWithErrorResultError,
|
|
31
|
+
PolicyExecutionCompletedWithWarningError,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"APIError",
|
|
36
|
+
"AcceldataSdkException",
|
|
37
|
+
"ApiException",
|
|
38
|
+
"BadRequestError",
|
|
39
|
+
"ConflictError",
|
|
40
|
+
"ForbiddenError",
|
|
41
|
+
"http_api_error_for_status",
|
|
42
|
+
"InvalidPipelineRunLookupError",
|
|
43
|
+
"InvalidPolicyTypeError",
|
|
44
|
+
"InvalidRuleTypeError",
|
|
45
|
+
"NotFoundError",
|
|
46
|
+
"OpenApiException",
|
|
47
|
+
"PipelineRunNotFoundError",
|
|
48
|
+
"PolicyExecutionCompletedWithErrorResultError",
|
|
49
|
+
"PolicyExecutionCompletedWithWarningError",
|
|
50
|
+
"ServiceError",
|
|
51
|
+
"UnauthorizedError",
|
|
52
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from acceldata.exceptions.exceptions import OpenApiException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class APIError(OpenApiException):
|
|
7
|
+
"""Raised when the ADOC HTTP API returns an error response."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
*,
|
|
12
|
+
message: str,
|
|
13
|
+
status_code: int,
|
|
14
|
+
response: object,
|
|
15
|
+
url: str,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
self.message = message
|
|
19
|
+
self.status_code = status_code
|
|
20
|
+
self.response = response
|
|
21
|
+
self.url = url
|
|
22
|
+
|
|
23
|
+
def __str__(self) -> str:
|
|
24
|
+
return f"{self.status_code} {type(self).__name__} for {self.url}: {self.message}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BadRequestError(APIError):
|
|
28
|
+
"""HTTP 400 — bad request."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class UnauthorizedError(APIError):
|
|
32
|
+
"""HTTP 401 — unauthorized."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ForbiddenError(APIError):
|
|
36
|
+
"""HTTP 403 — forbidden."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NotFoundError(APIError):
|
|
40
|
+
"""HTTP 404 — resource not found."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ConflictError(APIError):
|
|
44
|
+
"""HTTP 409 — resource conflict."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class ServiceError(APIError):
|
|
48
|
+
"""HTTP 5xx — server error."""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def http_api_error_for_status(
|
|
52
|
+
*,
|
|
53
|
+
status_code: int,
|
|
54
|
+
message: str,
|
|
55
|
+
response: object,
|
|
56
|
+
url: str,
|
|
57
|
+
) -> APIError:
|
|
58
|
+
"""Return the most specific :class:`APIError` subclass for ``status_code``."""
|
|
59
|
+
if status_code == 400:
|
|
60
|
+
return BadRequestError(
|
|
61
|
+
message=message, status_code=status_code, response=response, url=url
|
|
62
|
+
)
|
|
63
|
+
if status_code == 401:
|
|
64
|
+
return UnauthorizedError(
|
|
65
|
+
message=message, status_code=status_code, response=response, url=url
|
|
66
|
+
)
|
|
67
|
+
if status_code == 403:
|
|
68
|
+
return ForbiddenError(
|
|
69
|
+
message=message, status_code=status_code, response=response, url=url
|
|
70
|
+
)
|
|
71
|
+
if status_code == 404:
|
|
72
|
+
return NotFoundError(
|
|
73
|
+
message=message, status_code=status_code, response=response, url=url
|
|
74
|
+
)
|
|
75
|
+
if status_code == 409:
|
|
76
|
+
return ConflictError(
|
|
77
|
+
message=message, status_code=status_code, response=response, url=url
|
|
78
|
+
)
|
|
79
|
+
if 500 <= status_code < 600:
|
|
80
|
+
return ServiceError(
|
|
81
|
+
message=message, status_code=status_code, response=response, url=url
|
|
82
|
+
)
|
|
83
|
+
return APIError(
|
|
84
|
+
message=message, status_code=status_code, response=response, url=url
|
|
85
|
+
)
|