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.
Files changed (303) hide show
  1. acceldata/__init__.py +7 -0
  2. acceldata/client/__init__.py +0 -0
  3. acceldata/client/adoc_client.py +666 -0
  4. acceldata/client/http_client.py +346 -0
  5. acceldata/client/transient_retry.py +142 -0
  6. acceldata/constants/__init__.py +1 -0
  7. acceldata/constants/constants.py +13 -0
  8. acceldata/exceptions/__init__.py +52 -0
  9. acceldata/exceptions/api_errors.py +85 -0
  10. acceldata/exceptions/exceptions.py +174 -0
  11. acceldata/exceptions/sdk_exceptions.py +98 -0
  12. acceldata/mappers/__init__.py +21 -0
  13. acceldata/mappers/catalog_executions.py +107 -0
  14. acceldata/models/__init__.py +0 -0
  15. acceldata/models/api/catalog/above_configs.py +99 -0
  16. acceldata/models/api/catalog/above_configs_success.py +138 -0
  17. acceldata/models/api/catalog/absolute_configs.py +197 -0
  18. acceldata/models/api/catalog/absolute_configs_fallback.py +101 -0
  19. acceldata/models/api/catalog/absolute_threshold_config.py +105 -0
  20. acceldata/models/api/catalog/analysis_job_context.py +102 -0
  21. acceldata/models/api/catalog/analytics_pipeline.py +385 -0
  22. acceldata/models/api/catalog/analytics_pipeline_endpoint.py +136 -0
  23. acceldata/models/api/catalog/anomaly_config.py +95 -0
  24. acceldata/models/api/catalog/anomaly_details.py +119 -0
  25. acceldata/models/api/catalog/anomaly_threshold_config.py +105 -0
  26. acceldata/models/api/catalog/artifact.py +92 -0
  27. acceldata/models/api/catalog/assembly.py +171 -0
  28. acceldata/models/api/catalog/asset.py +162 -0
  29. acceldata/models/api/catalog/asset_catalog_listing.py +159 -0
  30. acceldata/models/api/catalog/asset_catalog_listing_response.py +102 -0
  31. acceldata/models/api/catalog/asset_label_request.py +96 -0
  32. acceldata/models/api/catalog/asset_progress.py +96 -0
  33. acceldata/models/api/catalog/asset_progress_details.py +117 -0
  34. acceldata/models/api/catalog/asset_tag.py +124 -0
  35. acceldata/models/api/catalog/asset_tag_info.py +140 -0
  36. acceldata/models/api/catalog/asset_tag_request.py +95 -0
  37. acceldata/models/api/catalog/asset_tag_response.py +96 -0
  38. acceldata/models/api/catalog/asset_tags_response.py +102 -0
  39. acceldata/models/api/catalog/asset_type.py +96 -0
  40. acceldata/models/api/catalog/auto_anomaly_feature_analysis.py +160 -0
  41. acceldata/models/api/catalog/auto_anomaly_item_execution.py +133 -0
  42. acceldata/models/api/catalog/auto_anomaly_meta_data.py +162 -0
  43. acceldata/models/api/catalog/auto_anomaly_rule_execution.py +102 -0
  44. acceldata/models/api/catalog/auto_anomaly_summary.py +124 -0
  45. acceldata/models/api/catalog/backing_asset.py +126 -0
  46. acceldata/models/api/catalog/below_configs.py +99 -0
  47. acceldata/models/api/catalog/big_decimal.py +100 -0
  48. acceldata/models/api/catalog/big_integer.py +98 -0
  49. acceldata/models/api/catalog/bounds_date_time_marker_config.py +110 -0
  50. acceldata/models/api/catalog/bounds_file_event_marker_config.py +108 -0
  51. acceldata/models/api/catalog/bounds_id_marker_config.py +96 -0
  52. acceldata/models/api/catalog/catalog_asset_meta_data.py +128 -0
  53. acceldata/models/api/catalog/changes_by_config.py +105 -0
  54. acceldata/models/api/catalog/column_mapping.py +139 -0
  55. acceldata/models/api/catalog/column_metadata.py +90 -0
  56. acceldata/models/api/catalog/column_metric_with_anomaly_details.py +119 -0
  57. acceldata/models/api/catalog/column_variable.py +111 -0
  58. acceldata/models/api/catalog/composite_policy_execution.py +102 -0
  59. acceldata/models/api/catalog/composite_policy_item_execution.py +104 -0
  60. acceldata/models/api/catalog/connection.py +118 -0
  61. acceldata/models/api/catalog/connection_test_result.py +106 -0
  62. acceldata/models/api/catalog/connection_type.py +90 -0
  63. acceldata/models/api/catalog/connection_types_response.py +96 -0
  64. acceldata/models/api/catalog/crawler.py +88 -0
  65. acceldata/models/api/catalog/crawler_action_response.py +90 -0
  66. acceldata/models/api/catalog/crawler_execution.py +147 -0
  67. acceldata/models/api/catalog/crawler_execution_response.py +102 -0
  68. acceldata/models/api/catalog/crawler_status_response.py +90 -0
  69. acceldata/models/api/catalog/create_profile.py +194 -0
  70. acceldata/models/api/catalog/custom_sql_config.py +121 -0
  71. acceldata/models/api/catalog/data_bricks_spark_resource_config.py +104 -0
  72. acceldata/models/api/catalog/data_cadence_rule.py +236 -0
  73. acceldata/models/api/catalog/data_cadence_rule_details.py +125 -0
  74. acceldata/models/api/catalog/data_cadence_rule_execution.py +102 -0
  75. acceldata/models/api/catalog/data_cadence_rule_item.py +104 -0
  76. acceldata/models/api/catalog/data_cadence_rule_item_execution.py +158 -0
  77. acceldata/models/api/catalog/data_cadence_rule_response.py +98 -0
  78. acceldata/models/api/catalog/data_drift_execution.py +102 -0
  79. acceldata/models/api/catalog/data_drift_item_execution.py +121 -0
  80. acceldata/models/api/catalog/data_quality_engine.py +90 -0
  81. acceldata/models/api/catalog/data_quality_execution.py +106 -0
  82. acceldata/models/api/catalog/data_quality_rule.py +323 -0
  83. acceldata/models/api/catalog/data_quality_rule_details.py +186 -0
  84. acceldata/models/api/catalog/data_quality_rule_response.py +120 -0
  85. acceldata/models/api/catalog/data_quality_rules_response.py +102 -0
  86. acceldata/models/api/catalog/databricks_compute_config.py +113 -0
  87. acceldata/models/api/catalog/dataplane_engine.py +105 -0
  88. acceldata/models/api/catalog/dataplane_health_metrics.py +101 -0
  89. acceldata/models/api/catalog/dataplane_job.py +216 -0
  90. acceldata/models/api/catalog/dataplane_job_status.py +113 -0
  91. acceldata/models/api/catalog/dataplane_request.py +96 -0
  92. acceldata/models/api/catalog/dataproc_compute_config.py +106 -0
  93. acceldata/models/api/catalog/dataproc_spark_resource_config.py +104 -0
  94. acceldata/models/api/catalog/date_based_partition_marker_config.py +116 -0
  95. acceldata/models/api/catalog/date_partitioning_marker_config.py +113 -0
  96. acceldata/models/api/catalog/date_time_marker_config.py +109 -0
  97. acceldata/models/api/catalog/day_month_year_based_partition_marker_config.py +124 -0
  98. acceldata/models/api/catalog/decreased_by_config.py +105 -0
  99. acceldata/models/api/catalog/delete_artifact_response.py +88 -0
  100. acceldata/models/api/catalog/dq_rule_execution.py +248 -0
  101. acceldata/models/api/catalog/drift.py +121 -0
  102. acceldata/models/api/catalog/enable_rule_request.py +88 -0
  103. acceldata/models/api/catalog/engine.py +90 -0
  104. acceldata/models/api/catalog/execution_cancellation_response.py +92 -0
  105. acceldata/models/api/catalog/execution_response.py +144 -0
  106. acceldata/models/api/catalog/execution_result.py +126 -0
  107. acceldata/models/api/catalog/executions_response.py +102 -0
  108. acceldata/models/api/catalog/failed_asset_detail.py +90 -0
  109. acceldata/models/api/catalog/failed_summary.py +98 -0
  110. acceldata/models/api/catalog/file.py +94 -0
  111. acceldata/models/api/catalog/file_marker_config.py +99 -0
  112. acceldata/models/api/catalog/generic_config_fallback.py +101 -0
  113. acceldata/models/api/catalog/generic_execution.py +221 -0
  114. acceldata/models/api/catalog/generic_rule_execution.py +137 -0
  115. acceldata/models/api/catalog/global_storage_config_dto.py +172 -0
  116. acceldata/models/api/catalog/hosted_dp_deployment_response.py +98 -0
  117. acceldata/models/api/catalog/id_marker_config.py +94 -0
  118. acceldata/models/api/catalog/increased_by_config.py +105 -0
  119. acceldata/models/api/catalog/item.py +160 -0
  120. acceldata/models/api/catalog/item_execution.py +192 -0
  121. acceldata/models/api/catalog/item_segment.py +131 -0
  122. acceldata/models/api/catalog/job_schedule.py +118 -0
  123. acceldata/models/api/catalog/job_spec.py +96 -0
  124. acceldata/models/api/catalog/kubernetes_metrics.py +91 -0
  125. acceldata/models/api/catalog/label.py +97 -0
  126. acceldata/models/api/catalog/livy_compute_config.py +103 -0
  127. acceldata/models/api/catalog/livy_spark_resource_config.py +106 -0
  128. acceldata/models/api/catalog/look_back_config.py +90 -0
  129. acceldata/models/api/catalog/manual_tag_request.py +92 -0
  130. acceldata/models/api/catalog/mapping.py +97 -0
  131. acceldata/models/api/catalog/marker.py +88 -0
  132. acceldata/models/api/catalog/marker_config.py +349 -0
  133. acceldata/models/api/catalog/marker_config_fallback.py +101 -0
  134. acceldata/models/api/catalog/meta.py +92 -0
  135. acceldata/models/api/catalog/meta_data.py +92 -0
  136. acceldata/models/api/catalog/meta_data_item.py +94 -0
  137. acceldata/models/api/catalog/mini_profile_request.py +112 -0
  138. acceldata/models/api/catalog/notification_payload.py +114 -0
  139. acceldata/models/api/catalog/offset_based_marker_config.py +97 -0
  140. acceldata/models/api/catalog/operational_config.py +92 -0
  141. acceldata/models/api/catalog/pattern_configuration.py +90 -0
  142. acceldata/models/api/catalog/pipe_line_request.py +92 -0
  143. acceldata/models/api/catalog/pipeline_compute_config.py +110 -0
  144. acceldata/models/api/catalog/policy_execution_marker_config.py +94 -0
  145. acceldata/models/api/catalog/policy_execution_request.py +229 -0
  146. acceldata/models/api/catalog/policy_group.py +114 -0
  147. acceldata/models/api/catalog/policy_transform_udf.py +113 -0
  148. acceldata/models/api/catalog/process_metrics.py +91 -0
  149. acceldata/models/api/catalog/profile_anomaly_details_response.py +109 -0
  150. acceldata/models/api/catalog/profile_anomaly_item_execution.py +127 -0
  151. acceldata/models/api/catalog/profile_anomaly_rule_execution.py +102 -0
  152. acceldata/models/api/catalog/profile_anomaly_rule_item.py +109 -0
  153. acceldata/models/api/catalog/profile_cancellation_response.py +92 -0
  154. acceldata/models/api/catalog/profile_request.py +211 -0
  155. acceldata/models/api/catalog/profile_request_response.py +92 -0
  156. acceldata/models/api/catalog/property_template.py +178 -0
  157. acceldata/models/api/catalog/property_templates.py +123 -0
  158. acceldata/models/api/catalog/pub_sub_lookback_marker_config.py +96 -0
  159. acceldata/models/api/catalog/pub_sub_timestamp_marker_config.py +108 -0
  160. acceldata/models/api/catalog/quality_rule.py +381 -0
  161. acceldata/models/api/catalog/quality_rule_execution_metrics.py +114 -0
  162. acceldata/models/api/catalog/quality_rule_with_latest_execution.py +115 -0
  163. acceldata/models/api/catalog/range_configs.py +109 -0
  164. acceldata/models/api/catalog/reconciliation_item.py +102 -0
  165. acceldata/models/api/catalog/reconciliation_item_execution.py +157 -0
  166. acceldata/models/api/catalog/reconciliation_rule.py +339 -0
  167. acceldata/models/api/catalog/reconciliation_rule_details.py +225 -0
  168. acceldata/models/api/catalog/reconciliation_rule_execution.py +102 -0
  169. acceldata/models/api/catalog/reconciliation_rule_response.py +98 -0
  170. acceldata/models/api/catalog/reconciliation_rules_response.py +102 -0
  171. acceldata/models/api/catalog/relative_config.py +197 -0
  172. acceldata/models/api/catalog/relative_config_fallback.py +101 -0
  173. acceldata/models/api/catalog/relative_threshold_config.py +111 -0
  174. acceldata/models/api/catalog/root_cause_analysis.py +108 -0
  175. acceldata/models/api/catalog/rule_execution.py +327 -0
  176. acceldata/models/api/catalog/rule_execution_result.py +90 -0
  177. acceldata/models/api/catalog/rule_item_result.py +125 -0
  178. acceldata/models/api/catalog/rule_meta_data.py +94 -0
  179. acceldata/models/api/catalog/rule_spark_sql_dynamic_filter_variable_mapping.py +100 -0
  180. acceldata/models/api/catalog/rule_tag.py +101 -0
  181. acceldata/models/api/catalog/rule_threshold_configuration.py +236 -0
  182. acceldata/models/api/catalog/rule_threshold_configuration_fallback.py +101 -0
  183. acceldata/models/api/catalog/rules_with_latest_execution_response.py +102 -0
  184. acceldata/models/api/catalog/sample_data_api_response.py +122 -0
  185. acceldata/models/api/catalog/schema_drift_item_execution.py +131 -0
  186. acceldata/models/api/catalog/schema_drift_rule_execution.py +102 -0
  187. acceldata/models/api/catalog/secret_manager_configuration.py +90 -0
  188. acceldata/models/api/catalog/secret_manager_configuration_with_option.py +98 -0
  189. acceldata/models/api/catalog/security_config.py +90 -0
  190. acceldata/models/api/catalog/segment_metric_json.py +142 -0
  191. acceldata/models/api/catalog/segments_analysis.py +145 -0
  192. acceldata/models/api/catalog/short_segment.py +97 -0
  193. acceldata/models/api/catalog/simple_quality_rule_response.py +92 -0
  194. acceldata/models/api/catalog/snapshot_data.py +117 -0
  195. acceldata/models/api/catalog/source_model.py +90 -0
  196. acceldata/models/api/catalog/source_type.py +100 -0
  197. acceldata/models/api/catalog/source_types_response.py +102 -0
  198. acceldata/models/api/catalog/spark_job_status.py +113 -0
  199. acceldata/models/api/catalog/spark_resource_config.py +114 -0
  200. acceldata/models/api/catalog/standard_response_assembly.py +92 -0
  201. acceldata/models/api/catalog/standard_response_crawler_action_response.py +92 -0
  202. acceldata/models/api/catalog/standard_response_crawler_status_response.py +92 -0
  203. acceldata/models/api/catalog/standard_response_delete_artifact_response.py +92 -0
  204. acceldata/models/api/catalog/standard_response_list.py +96 -0
  205. acceldata/models/api/catalog/standard_response_map.py +88 -0
  206. acceldata/models/api/catalog/standard_response_meta_data.py +92 -0
  207. acceldata/models/api/catalog/standard_response_mini_profile_request.py +92 -0
  208. acceldata/models/api/catalog/standard_response_profile_request.py +92 -0
  209. acceldata/models/api/catalog/start_profiling_request.py +92 -0
  210. acceldata/models/api/catalog/tag_domain.py +116 -0
  211. acceldata/models/api/catalog/threshold_breach_details.py +101 -0
  212. acceldata/models/api/catalog/time_based_config.py +92 -0
  213. acceldata/models/api/catalog/time_based_threshold_config.py +105 -0
  214. acceldata/models/api/catalog/timestamp_based_marker_config.py +115 -0
  215. acceldata/models/api/catalog/udf_variable.py +97 -0
  216. acceldata/models/api/catalog/yunikorn_compute_config.py +133 -0
  217. acceldata/models/api/catalog/yunikorn_spark_resource_config.py +154 -0
  218. acceldata/models/api/pipeline/big_decimal.py +100 -0
  219. acceldata/models/api/pipeline/big_integer.py +98 -0
  220. acceldata/models/api/pipeline/child_span_response.py +96 -0
  221. acceldata/models/api/pipeline/create_span_event.py +109 -0
  222. acceldata/models/api/pipeline/edge.py +108 -0
  223. acceldata/models/api/pipeline/event_alert_object.py +95 -0
  224. acceldata/models/api/pipeline/generic_config_fallback.py +101 -0
  225. acceldata/models/api/pipeline/identifier.py +146 -0
  226. acceldata/models/api/pipeline/job_request.py +130 -0
  227. acceldata/models/api/pipeline/meta.py +107 -0
  228. acceldata/models/api/pipeline/meta_data.py +92 -0
  229. acceldata/models/api/pipeline/metric.py +103 -0
  230. acceldata/models/api/pipeline/node.py +201 -0
  231. acceldata/models/api/pipeline/node_response.py +92 -0
  232. acceldata/models/api/pipeline/notification_payload.py +114 -0
  233. acceldata/models/api/pipeline/pipeline.py +152 -0
  234. acceldata/models/api/pipeline/pipeline_baseline_metric.py +92 -0
  235. acceldata/models/api/pipeline/pipeline_details.py +118 -0
  236. acceldata/models/api/pipeline/pipeline_details_response.py +102 -0
  237. acceldata/models/api/pipeline/pipeline_input_request.py +92 -0
  238. acceldata/models/api/pipeline/pipeline_listing_info.py +118 -0
  239. acceldata/models/api/pipeline/pipeline_response.py +92 -0
  240. acceldata/models/api/pipeline/pipeline_run.py +136 -0
  241. acceldata/models/api/pipeline/pipeline_run_request.py +92 -0
  242. acceldata/models/api/pipeline/pipeline_runs_response.py +102 -0
  243. acceldata/models/api/pipeline/pipeline_summary.py +98 -0
  244. acceldata/models/api/pipeline/pipelines_listing_response.py +102 -0
  245. acceldata/models/api/pipeline/policy_summary.py +102 -0
  246. acceldata/models/api/pipeline/span.py +115 -0
  247. acceldata/models/api/pipeline/span_event.py +114 -0
  248. acceldata/models/api/pipeline/span_event_request.py +92 -0
  249. acceldata/models/api/pipeline/span_event_response.py +92 -0
  250. acceldata/models/api/pipeline/span_request.py +94 -0
  251. acceldata/models/api/pipeline/spans_response.py +96 -0
  252. acceldata/models/api/pipeline/tag.py +97 -0
  253. acceldata/models/sdk/__init__.py +0 -0
  254. acceldata/models/sdk/catalog/__init__.py +105 -0
  255. acceldata/models/sdk/catalog/asset_resource.py +112 -0
  256. acceldata/models/sdk/catalog/asset_source_type.py +59 -0
  257. acceldata/models/sdk/catalog/custom_asset_metadata.py +11 -0
  258. acceldata/models/sdk/catalog/datasource_resource.py +88 -0
  259. acceldata/models/sdk/catalog/executor.py +524 -0
  260. acceldata/models/sdk/catalog/policy_execution_request.py +114 -0
  261. acceldata/models/sdk/catalog/policy_execution_type.py +16 -0
  262. acceldata/models/sdk/catalog/policy_filter.py +94 -0
  263. acceldata/models/sdk/catalog/policy_rule_resources.py +388 -0
  264. acceldata/models/sdk/catalog/profile_request_resource.py +77 -0
  265. acceldata/models/sdk/catalog/profiling.py +82 -0
  266. acceldata/models/sdk/catalog/profiling_configuration_error.py +5 -0
  267. acceldata/models/sdk/pipeline/__init__.py +62 -0
  268. acceldata/models/sdk/pipeline/create_job_input.py +108 -0
  269. acceldata/models/sdk/pipeline/create_pipeline_input_request.py +72 -0
  270. acceldata/models/sdk/pipeline/events/__init__.py +5 -0
  271. acceldata/models/sdk/pipeline/events/generic_event.py +31 -0
  272. acceldata/models/sdk/pipeline/events/log_event.py +34 -0
  273. acceldata/models/sdk/pipeline/events/span_event.py +8 -0
  274. acceldata/models/sdk/pipeline/pipeline_resource.py +171 -0
  275. acceldata/models/sdk/pipeline/pipeline_run_input.py +84 -0
  276. acceldata/models/sdk/pipeline/pipeline_run_resource.py +208 -0
  277. acceldata/models/sdk/pipeline/pipeline_source_type.py +20 -0
  278. acceldata/models/sdk/pipeline/span_resource.py +327 -0
  279. acceldata/openapi_deserialization_patches.py +161 -0
  280. acceldata/openapi_repr_patches.py +62 -0
  281. acceldata/services/__init__.py +0 -0
  282. acceldata/services/api_timestamp.py +31 -0
  283. acceldata/services/asset_service.py +651 -0
  284. acceldata/services/datasource_service.py +120 -0
  285. acceldata/services/generic_service.py +132 -0
  286. acceldata/services/marker_config.py +232 -0
  287. acceldata/services/pipeline_big_decimal_api.py +102 -0
  288. acceldata/services/pipeline_response_parse.py +209 -0
  289. acceldata/services/pipeline_service.py +494 -0
  290. acceldata/services/rule_service.py +620 -0
  291. acceldata/urls/__init__.py +1 -0
  292. acceldata/urls/catalog_urls.py +137 -0
  293. acceldata/urls/pipeline_urls.py +55 -0
  294. acceldata/utils/__init__.py +6 -0
  295. acceldata/utils/datetime_utils.py +16 -0
  296. acceldata/utils/operation_ack.py +22 -0
  297. acceldata/utils/span_helpers.py +95 -0
  298. acceldata/utils/time_range_utils.py +90 -0
  299. acceldata_sdk_python-26.7.0.dist-info/METADATA +26 -0
  300. acceldata_sdk_python-26.7.0.dist-info/RECORD +303 -0
  301. acceldata_sdk_python-26.7.0.dist-info/WHEEL +5 -0
  302. acceldata_sdk_python-26.7.0.dist-info/licenses/LICENSE +22 -0
  303. 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
+ )