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,620 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import ValidationError
|
|
7
|
+
|
|
8
|
+
from acceldata.client.http_client import BaseHttpClient
|
|
9
|
+
from acceldata.mappers.catalog_executions import (
|
|
10
|
+
catalog_generic_execution_branch_typename,
|
|
11
|
+
catalog_generic_execution_payload,
|
|
12
|
+
)
|
|
13
|
+
from acceldata.models.api.catalog.data_cadence_rule_response import DataCadenceRuleResponse
|
|
14
|
+
from acceldata.models.api.catalog.data_quality_execution import DataQualityExecution
|
|
15
|
+
from acceldata.models.api.catalog.data_quality_rule_response import DataQualityRuleResponse
|
|
16
|
+
from acceldata.models.api.catalog.enable_rule_request import EnableRuleRequest
|
|
17
|
+
from acceldata.models.api.catalog.execution_cancellation_response import ExecutionCancellationResponse
|
|
18
|
+
from acceldata.models.api.catalog.execution_result import ExecutionResult
|
|
19
|
+
from acceldata.models.api.catalog.generic_execution import GenericExecution
|
|
20
|
+
from acceldata.models.api.catalog.policy_execution_request import (
|
|
21
|
+
PolicyExecutionRequest as CatalogPolicyExecutionRequest,
|
|
22
|
+
)
|
|
23
|
+
from acceldata.models.api.catalog.data_cadence_rule_execution import DataCadenceRuleExecution
|
|
24
|
+
from acceldata.models.api.catalog.data_drift_execution import DataDriftExecution
|
|
25
|
+
from acceldata.models.api.catalog.reconciliation_rule_execution import ReconciliationRuleExecution
|
|
26
|
+
from acceldata.models.api.catalog.schema_drift_rule_execution import SchemaDriftRuleExecution
|
|
27
|
+
from acceldata.models.sdk.catalog.policy_execution_request import (
|
|
28
|
+
PolicyExecutionRequestInput,
|
|
29
|
+
ensure_catalog_policy_execution_request,
|
|
30
|
+
)
|
|
31
|
+
from acceldata.models.sdk.catalog.policy_execution_type import PolicyExecutionType
|
|
32
|
+
from acceldata.models.api.catalog.quality_rule import QualityRule
|
|
33
|
+
from acceldata.models.api.catalog.reconciliation_rule_response import ReconciliationRuleResponse
|
|
34
|
+
from acceldata.models.api.catalog.rules_with_latest_execution_response import \
|
|
35
|
+
RulesWithLatestExecutionResponse
|
|
36
|
+
from acceldata.models.sdk.catalog.policy_filter import PolicyFilter, RuleType
|
|
37
|
+
from acceldata.utils.time_range_utils import TimeRangeCalculator
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger("sdk")
|
|
40
|
+
|
|
41
|
+
# POST …/rules/{kind}/{id}/executions — each endpoint’s body matches one ``GenericExecution`` branch.
|
|
42
|
+
# Use concrete execution DTO types from ``acceldata.models.api.catalog`` that match the wire shape,
|
|
43
|
+
# or ``GenericExecution(typed)`` validation fails.
|
|
44
|
+
_EXECUTE_RESPONSE_MODEL = {
|
|
45
|
+
"execute_dq_rule": DataQualityExecution,
|
|
46
|
+
"execute_reconciliation_rule": ReconciliationRuleExecution,
|
|
47
|
+
"execute_freshness_rule": DataCadenceRuleExecution,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# GET ``.../rules/.../executions`` list rows match exactly one ``GenericExecution`` branch; avoid
|
|
51
|
+
# ``GenericExecution.from_dict`` here — the oneOf deserializer can match several branches for the same JSON.
|
|
52
|
+
_POLICY_EXECUTIONS_LIST_ROW_MODEL: dict[RuleType, type] = {
|
|
53
|
+
RuleType.DATA_QUALITY: DataQualityExecution,
|
|
54
|
+
RuleType.RECONCILIATION: ReconciliationRuleExecution,
|
|
55
|
+
RuleType.EQUALITY: ReconciliationRuleExecution,
|
|
56
|
+
RuleType.DATA_DRIFT: DataDriftExecution,
|
|
57
|
+
RuleType.SCHEMA_DRIFT: SchemaDriftRuleExecution,
|
|
58
|
+
RuleType.DATA_CADENCE: DataCadenceRuleExecution,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _normalize_catalog_policy_execution_row(row: dict[str, Any]) -> dict[str, Any]:
|
|
63
|
+
"""Catalog execution list rows may omit ``items`` or send ``null``; typed API DTOs expect a list."""
|
|
64
|
+
normalized = dict(row)
|
|
65
|
+
if normalized.get("items") is None:
|
|
66
|
+
normalized["items"] = []
|
|
67
|
+
return normalized
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _generic_execution_from_policy_executions_row(row: dict[str, Any], rule_type: RuleType) -> GenericExecution:
|
|
71
|
+
model_cls = _POLICY_EXECUTIONS_LIST_ROW_MODEL.get(rule_type)
|
|
72
|
+
if model_cls is None:
|
|
73
|
+
raise ValueError(f"policy_executions: unsupported rule_type {rule_type!r}")
|
|
74
|
+
row = _normalize_catalog_policy_execution_row(row)
|
|
75
|
+
try:
|
|
76
|
+
typed = model_cls.from_dict(row)
|
|
77
|
+
except (ValidationError, ValueError, TypeError) as strict_exc:
|
|
78
|
+
# Newer / looser list rows: same outer shape may no longer match the exact typed
|
|
79
|
+
# wrapper, but the payload may still be a valid :class:`GenericExecution` oneOf. Mirrors
|
|
80
|
+
# the POST ``_parse_generic_execution_response`` fallback to ``GenericExecution.from_dict``.
|
|
81
|
+
try:
|
|
82
|
+
ge = GenericExecution.from_dict(row)
|
|
83
|
+
except (ValidationError, ValueError) as loose_exc:
|
|
84
|
+
raise TypeError(
|
|
85
|
+
f"policy_executions could not deserialize row as {model_cls.__name__} "
|
|
86
|
+
f"and GenericExecution oneOf also failed. Strict: {strict_exc}. OneOf: {loose_exc}"
|
|
87
|
+
) from strict_exc
|
|
88
|
+
branch = catalog_generic_execution_payload(ge)
|
|
89
|
+
if branch is None:
|
|
90
|
+
raise TypeError(
|
|
91
|
+
f"policy_executions: GenericExecution fallback deserialised empty (expected for row keys "
|
|
92
|
+
f"{sorted(row.keys())!r})"
|
|
93
|
+
) from strict_exc
|
|
94
|
+
logger.debug(
|
|
95
|
+
"policy_executions: row was not a strict %s; deserialized as GenericExecution (oneOf: %s)",
|
|
96
|
+
model_cls.__name__,
|
|
97
|
+
catalog_generic_execution_branch_typename(ge),
|
|
98
|
+
)
|
|
99
|
+
return ge
|
|
100
|
+
if typed is None:
|
|
101
|
+
raise TypeError(f"policy_executions: empty deserialization for {model_cls.__name__}")
|
|
102
|
+
if not isinstance(typed, model_cls):
|
|
103
|
+
# ``from_dict`` should return ``model_cls``; keep behavior resilient for mocked/stubbed returns.
|
|
104
|
+
try:
|
|
105
|
+
return GenericExecution(typed)
|
|
106
|
+
except (ValidationError, ValueError, TypeError):
|
|
107
|
+
logger.debug(
|
|
108
|
+
"policy_executions: using unvalidated GenericExecution wrapper for %s.from_dict return type %s",
|
|
109
|
+
model_cls.__name__,
|
|
110
|
+
type(typed).__name__,
|
|
111
|
+
)
|
|
112
|
+
return GenericExecution.model_construct(actual_instance=typed)
|
|
113
|
+
return GenericExecution(typed)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _describe_generic_execution_parse_failure(exc: BaseException) -> str:
|
|
117
|
+
"""One-line classification for GenericExecution ``oneOf`` failures (avoid huge pydantic dumps in logs)."""
|
|
118
|
+
msg = str(exc)
|
|
119
|
+
if "Multiple matches found" in msg:
|
|
120
|
+
return (
|
|
121
|
+
"GenericExecution oneOf is ambiguous (several branches matched). "
|
|
122
|
+
"Common when the API omits nulls for nested item fields while the client model schema marks those "
|
|
123
|
+
"properties as required without defaults — fix the spec / annotations and regenerate the client."
|
|
124
|
+
)
|
|
125
|
+
if "No match found" in msg:
|
|
126
|
+
return "GenericExecution oneOf: no branch matched this JSON shape."
|
|
127
|
+
return f"{type(exc).__name__}: {msg[:200]}{'…' if len(msg) > 200 else ''}"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Path segment under ``GET /rules/{segment}/{ruleId}/executions`` (kebab-case).
|
|
131
|
+
# Data-quality and reconciliation use these typed paths; all other kinds use
|
|
132
|
+
# ``GET /rules/executions`` with ``ruleId``; see ``_uses_rules_executions_endpoint``.
|
|
133
|
+
_RULE_TYPE_TO_EXECUTIONS_PATH_SEGMENT: dict[RuleType, str] = {
|
|
134
|
+
RuleType.DATA_QUALITY: "data-quality",
|
|
135
|
+
RuleType.RECONCILIATION: "reconciliation",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _uses_rules_executions_endpoint(rule_type: RuleType) -> bool:
|
|
140
|
+
"""Whether to call ``GET /rules/executions`` with ``ruleId`` (any kind except data-quality / reconciliation)."""
|
|
141
|
+
return rule_type not in (RuleType.DATA_QUALITY, RuleType.RECONCILIATION)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class RuleService:
|
|
145
|
+
def __init__(self, http_client: BaseHttpClient) -> None:
|
|
146
|
+
self._client = http_client
|
|
147
|
+
|
|
148
|
+
def get_all_rules(
|
|
149
|
+
self,
|
|
150
|
+
filter: PolicyFilter,
|
|
151
|
+
page: int = 0,
|
|
152
|
+
size: int = 25,
|
|
153
|
+
with_latest_execution: bool = True,
|
|
154
|
+
sort_by: str = "updatedAt:DESC",
|
|
155
|
+
) -> RulesWithLatestExecutionResponse:
|
|
156
|
+
params: dict[str, Any] = {
|
|
157
|
+
"page": page,
|
|
158
|
+
"size": size,
|
|
159
|
+
"withLatestExecution": with_latest_execution,
|
|
160
|
+
"sortBy": sort_by,
|
|
161
|
+
}
|
|
162
|
+
if filter.policy_type is not None:
|
|
163
|
+
params["ruleType"] = filter.policy_type.name
|
|
164
|
+
if filter.policy_type.name == RuleType.RECONCILIATION.name:
|
|
165
|
+
params["ruleType"] = params["ruleType"] + ",EQUALITY"
|
|
166
|
+
if filter.last_execution_result is not None:
|
|
167
|
+
params["resultStatus"] = filter.last_execution_result.name
|
|
168
|
+
if filter.enable is not None:
|
|
169
|
+
params["enabled"] = filter.enable
|
|
170
|
+
if filter.active is not None and filter.active is True:
|
|
171
|
+
params["ruleStatus"] = "ACTIVE"
|
|
172
|
+
else:
|
|
173
|
+
params["ruleStatus"] = "ALL"
|
|
174
|
+
if filter.assets is not None:
|
|
175
|
+
params["assetIds"] = ",".join([str(i.id) for i in filter.assets])
|
|
176
|
+
if filter.data_sources is not None:
|
|
177
|
+
params["assemblyIds"] = ",".join([str(i.id) for i in filter.data_sources])
|
|
178
|
+
if filter.period is not None:
|
|
179
|
+
finished_before_time, started_after_time = TimeRangeCalculator.calculate_time_range(filter)
|
|
180
|
+
if started_after_time is not None:
|
|
181
|
+
params["startedAfter"] = int(round(started_after_time.timestamp() * 1000))
|
|
182
|
+
params["finishedBefore"] = int(round(finished_before_time.timestamp() * 1000))
|
|
183
|
+
|
|
184
|
+
logger.debug(params)
|
|
185
|
+
url = self._client.catalog_urls.rules()
|
|
186
|
+
response = self._client._get(
|
|
187
|
+
url=url,
|
|
188
|
+
params=params,
|
|
189
|
+
)
|
|
190
|
+
logger.debug(f"Response: {response}")
|
|
191
|
+
return RulesWithLatestExecutionResponse.from_dict(response)
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _require_object_response(response: object, operation: str) -> dict[str, Any]:
|
|
195
|
+
if not isinstance(response, dict):
|
|
196
|
+
raise TypeError(f"{operation} expected a JSON object response, got {type(response).__name__}")
|
|
197
|
+
return response
|
|
198
|
+
|
|
199
|
+
def get_policy(self, identifier: str | int) -> QualityRule:
|
|
200
|
+
"""GET ``/rules/{identifier}`` — returns :class:`~acceldata.models.api.catalog.quality_rule.QualityRule`."""
|
|
201
|
+
url = self._client.catalog_urls.rule_by_identifier(identifier)
|
|
202
|
+
response = self._client._get(url=url)
|
|
203
|
+
raw = self._require_object_response(response, "get_policy")
|
|
204
|
+
parsed = QualityRule.from_dict(raw)
|
|
205
|
+
if parsed is None:
|
|
206
|
+
raise TypeError("get_policy received an empty QualityRule payload")
|
|
207
|
+
return parsed
|
|
208
|
+
|
|
209
|
+
def get_dq_rule(self, rule_id: str | int) -> DataQualityRuleResponse:
|
|
210
|
+
"""GET ``/rules/data-quality/{rule_id}`` — Data quality :class:`~acceldata.models.api.catalog.rule_response.RuleResponse`."""
|
|
211
|
+
url = self._client.catalog_urls.data_quality_rule(rule_id)
|
|
212
|
+
response = self._client._get(url=url)
|
|
213
|
+
raw = self._require_object_response(response, "get_dq_rule")
|
|
214
|
+
logger.info(f"Raw dq rule: {raw} ")
|
|
215
|
+
parsed = DataQualityRuleResponse.from_dict(raw)
|
|
216
|
+
logger.debug(f"DataQualityRuleResponse: {parsed}")
|
|
217
|
+
if parsed is None:
|
|
218
|
+
raise TypeError("get_dq_rule received an empty RuleResponse payload")
|
|
219
|
+
return parsed
|
|
220
|
+
|
|
221
|
+
def get_reconciliation_rule(self, rule_id: str | int) -> ReconciliationRuleResponse:
|
|
222
|
+
"""GET ``/rules/reconciliation/{rule_id}``."""
|
|
223
|
+
url = self._client.catalog_urls.reconciliation_rule(rule_id)
|
|
224
|
+
response = self._client._get(url=url)
|
|
225
|
+
raw = self._require_object_response(response, "get_reconciliation_rule")
|
|
226
|
+
parsed = ReconciliationRuleResponse.from_dict(raw)
|
|
227
|
+
if parsed is None:
|
|
228
|
+
raise TypeError("get_reconciliation_rule received an empty response payload")
|
|
229
|
+
return parsed
|
|
230
|
+
|
|
231
|
+
def get_freshness_rule(self, rule_id: str | int) -> DataCadenceRuleResponse:
|
|
232
|
+
"""GET ``/rules/data-cadence/{rule_id}`` (freshness / cadence rule)."""
|
|
233
|
+
url = self._client.catalog_urls.data_cadence_rule(rule_id)
|
|
234
|
+
response = self._client._get(url=url)
|
|
235
|
+
raw = self._require_object_response(response, "get_freshness_rule")
|
|
236
|
+
parsed = DataCadenceRuleResponse.from_dict(raw)
|
|
237
|
+
if parsed is None:
|
|
238
|
+
raise TypeError("get_freshness_rule received an empty response payload")
|
|
239
|
+
return parsed
|
|
240
|
+
|
|
241
|
+
def get_reconciliation_rule_execution_details(
|
|
242
|
+
self, execution_id: str | int
|
|
243
|
+
) -> ReconciliationRuleExecution:
|
|
244
|
+
"""GET ``/rules/reconciliation/executions/{execution_id}`` — Reconciliation.Execution payload."""
|
|
245
|
+
url = self._client.catalog_urls.reconciliation_rule_execution(execution_id)
|
|
246
|
+
response = self._client._get(url=url)
|
|
247
|
+
raw = self._require_object_response(response, "get_reconciliation_rule_execution_details")
|
|
248
|
+
parsed = ReconciliationRuleExecution.from_dict(raw)
|
|
249
|
+
if parsed is None:
|
|
250
|
+
raise TypeError("get_reconciliation_rule_execution_details received an empty response payload")
|
|
251
|
+
return parsed
|
|
252
|
+
|
|
253
|
+
def get_reconciliation_rule_result(self, execution_id: str | int) -> ExecutionResult:
|
|
254
|
+
"""GET ``/rules/reconciliation/executions/{execution_id}/result`` — :class:`ExecutionResult`."""
|
|
255
|
+
url = self._client.catalog_urls.reconciliation_rule_execution_result(execution_id)
|
|
256
|
+
response = self._client._get(url=url)
|
|
257
|
+
raw = self._require_object_response(response, "get_reconciliation_rule_result")
|
|
258
|
+
parsed = ExecutionResult.from_dict(raw)
|
|
259
|
+
if parsed is None:
|
|
260
|
+
raise TypeError("get_reconciliation_rule_result received an empty response payload")
|
|
261
|
+
return parsed
|
|
262
|
+
|
|
263
|
+
@staticmethod
|
|
264
|
+
def _identifier_is_numeric_rule_id(identifier: str | int) -> bool:
|
|
265
|
+
if isinstance(identifier, int):
|
|
266
|
+
return True
|
|
267
|
+
return TimeRangeCalculator.check_int(str(identifier))
|
|
268
|
+
|
|
269
|
+
def _resolve_rule_id_for_executions(self, identifier: str | int, rule_type: RuleType) -> str | int:
|
|
270
|
+
if self._identifier_is_numeric_rule_id(identifier):
|
|
271
|
+
return identifier
|
|
272
|
+
if rule_type == RuleType.DATA_QUALITY:
|
|
273
|
+
rule_res = self.get_dq_rule(identifier)
|
|
274
|
+
rid = rule_res.rule.id
|
|
275
|
+
elif rule_type in (RuleType.RECONCILIATION, RuleType.EQUALITY):
|
|
276
|
+
rule_res = self.get_reconciliation_rule(identifier)
|
|
277
|
+
rid = rule_res.rule.id
|
|
278
|
+
elif rule_type == RuleType.DATA_CADENCE:
|
|
279
|
+
rule_res = self.get_freshness_rule(identifier)
|
|
280
|
+
rid = rule_res.rule.id
|
|
281
|
+
elif _uses_rules_executions_endpoint(rule_type):
|
|
282
|
+
qr = self.get_policy(identifier)
|
|
283
|
+
rid = qr.id
|
|
284
|
+
else:
|
|
285
|
+
raise ValueError(
|
|
286
|
+
f"A numeric rule id is required for {rule_type.name}; "
|
|
287
|
+
"name-based lookup is not implemented for this rule kind."
|
|
288
|
+
)
|
|
289
|
+
if rid is None:
|
|
290
|
+
raise TypeError("Resolved rule has no id; cannot list executions.")
|
|
291
|
+
return rid
|
|
292
|
+
|
|
293
|
+
def _policy_executions_url(self, rule_type: RuleType, rule_id: str | int) -> str:
|
|
294
|
+
segment = _RULE_TYPE_TO_EXECUTIONS_PATH_SEGMENT.get(rule_type)
|
|
295
|
+
if segment is None:
|
|
296
|
+
raise ValueError(f"Unknown rule type for listing executions: {rule_type!r}")
|
|
297
|
+
base = self._client.catalog_urls.rules()
|
|
298
|
+
return f"{base}/{segment}/{rule_id}/executions"
|
|
299
|
+
|
|
300
|
+
@staticmethod
|
|
301
|
+
def _executions_rows_from_policy_list_response(raw: dict[str, Any]) -> list[Any]:
|
|
302
|
+
"""Normalize list payloads from GET ``.../rules/{{kind}}/{{id}}/executions``.
|
|
303
|
+
|
|
304
|
+
The API may return either a flat ``ExecutionsResponse`` shape
|
|
305
|
+
(``executions`` + ``meta`` at the top level) or a Kotlin-style wrapper whose
|
|
306
|
+
sole meaningful key ends with ``ExecutionsResponse``, e.g.
|
|
307
|
+
``DataQuality.ExecutionsResponse`` or ``Reconciliation.ExecutionsResponse``.
|
|
308
|
+
|
|
309
|
+
When the payload has no recognizable executions list (e.g. empty object or
|
|
310
|
+
only ``meta``), returns an empty list — the rule may never have been executed.
|
|
311
|
+
"""
|
|
312
|
+
if not raw:
|
|
313
|
+
return []
|
|
314
|
+
if "executions" in raw:
|
|
315
|
+
top = raw["executions"]
|
|
316
|
+
if top is None:
|
|
317
|
+
return []
|
|
318
|
+
if isinstance(top, list):
|
|
319
|
+
return top
|
|
320
|
+
raise TypeError(
|
|
321
|
+
"policy_executions expected top-level 'executions' to be a list or null, "
|
|
322
|
+
f"got {type(top).__name__}"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
for key, value in raw.items():
|
|
326
|
+
if not isinstance(value, dict):
|
|
327
|
+
continue
|
|
328
|
+
if not key.endswith("ExecutionsResponse"):
|
|
329
|
+
continue
|
|
330
|
+
nested = value.get("executions")
|
|
331
|
+
if nested is None:
|
|
332
|
+
return []
|
|
333
|
+
if isinstance(nested, list):
|
|
334
|
+
return nested
|
|
335
|
+
raise TypeError(
|
|
336
|
+
"policy_executions expected wrapped *ExecutionsResponse.executions to be a list, "
|
|
337
|
+
f"got {type(nested).__name__} (key {key!r})"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
logger.debug(
|
|
341
|
+
"policy_executions: no executions list in response (keys=%s); treating as no runs",
|
|
342
|
+
sorted(raw.keys()),
|
|
343
|
+
)
|
|
344
|
+
return []
|
|
345
|
+
|
|
346
|
+
def policy_executions(
|
|
347
|
+
self,
|
|
348
|
+
identifier: str | int,
|
|
349
|
+
rule_type: RuleType,
|
|
350
|
+
page: int = 0,
|
|
351
|
+
size: int = 25,
|
|
352
|
+
sort_by: str = "finishedAt:DESC",
|
|
353
|
+
) -> list[GenericExecution]:
|
|
354
|
+
"""List rule executions.
|
|
355
|
+
|
|
356
|
+
Uses ``GET /rules/{{kind}}/{{rule_id}}/executions`` for data-quality and reconciliation
|
|
357
|
+
rules only. All other kinds use ``GET /rules/executions`` with query param ``ruleId``
|
|
358
|
+
(same contract as the catalog UI).
|
|
359
|
+
|
|
360
|
+
Accepts both flat ``ExecutionsResponse`` JSON and wrapped shapes such as
|
|
361
|
+
``DataQuality.ExecutionsResponse`` / ``Reconciliation.ExecutionsResponse``.
|
|
362
|
+
|
|
363
|
+
Returns an empty list when the catalog has no executions yet (including a JSON
|
|
364
|
+
``null`` body parsed as ``None``, an empty object ``{}``, or a payload with no
|
|
365
|
+
``executions`` / ``*ExecutionsResponse`` list).
|
|
366
|
+
|
|
367
|
+
Each row is first parsed with the rule-kind–specific typed model (e.g. ``DataQualityExecution``).
|
|
368
|
+
If that fails, the same row is retried with :class:`GenericExecution` (``oneOf``), same strategy as
|
|
369
|
+
:meth:`execute_dq_rule` and :meth:`_parse_generic_execution_response` for execute responses.
|
|
370
|
+
"""
|
|
371
|
+
rule_id = self._resolve_rule_id_for_executions(identifier, rule_type)
|
|
372
|
+
if _uses_rules_executions_endpoint(rule_type):
|
|
373
|
+
url = self._client.catalog_urls.rules_executions()
|
|
374
|
+
params: dict[str, Any] = {
|
|
375
|
+
"page": page,
|
|
376
|
+
"size": size,
|
|
377
|
+
"sortBy": sort_by,
|
|
378
|
+
"ruleId": rule_id,
|
|
379
|
+
}
|
|
380
|
+
else:
|
|
381
|
+
url = self._policy_executions_url(rule_type, rule_id)
|
|
382
|
+
params = {
|
|
383
|
+
"page": page,
|
|
384
|
+
"size": size,
|
|
385
|
+
"sortBy": sort_by,
|
|
386
|
+
}
|
|
387
|
+
response = self._client._get(url=url, params=params)
|
|
388
|
+
logger.debug(f"Rule executions response: {response}")
|
|
389
|
+
if response is None:
|
|
390
|
+
return []
|
|
391
|
+
raw = self._require_object_response(response, "policy_executions")
|
|
392
|
+
rows = self._executions_rows_from_policy_list_response(raw)
|
|
393
|
+
row_model = _POLICY_EXECUTIONS_LIST_ROW_MODEL.get(rule_type)
|
|
394
|
+
if row_model is None:
|
|
395
|
+
raise ValueError(f"policy_executions: unsupported rule_type {rule_type!r}")
|
|
396
|
+
logger.debug(
|
|
397
|
+
"policy_executions: parsing %d row(s) as %s (rule_type=%s)",
|
|
398
|
+
len(rows),
|
|
399
|
+
row_model.__name__,
|
|
400
|
+
rule_type.name,
|
|
401
|
+
)
|
|
402
|
+
parsed: list[GenericExecution] = []
|
|
403
|
+
for row in rows:
|
|
404
|
+
if not isinstance(row, dict):
|
|
405
|
+
raise TypeError(
|
|
406
|
+
"policy_executions expected each row to be an object with an 'execution' field, "
|
|
407
|
+
f"got {type(row).__name__}"
|
|
408
|
+
)
|
|
409
|
+
if "execution" not in row:
|
|
410
|
+
raise TypeError("policy_executions row is missing required 'execution' field")
|
|
411
|
+
execution_raw = row["execution"]
|
|
412
|
+
if execution_raw is None:
|
|
413
|
+
raise TypeError("policy_executions row['execution'] is null")
|
|
414
|
+
if not isinstance(execution_raw, dict):
|
|
415
|
+
raise TypeError(
|
|
416
|
+
"policy_executions expected row['execution'] to be an object, "
|
|
417
|
+
f"got {type(execution_raw).__name__}"
|
|
418
|
+
)
|
|
419
|
+
# List rows match ``DataQualityExecution`` / ``ReconciliationRuleExecution`` etc. (execution +
|
|
420
|
+
# items + policy fields), not bare ``RuleExecution`` / ``GenericRuleExecution`` payloads.
|
|
421
|
+
ge = _generic_execution_from_policy_executions_row(row, rule_type)
|
|
422
|
+
if ge is None:
|
|
423
|
+
raise TypeError("policy_executions received empty execution payload")
|
|
424
|
+
parsed.append(ge)
|
|
425
|
+
return parsed
|
|
426
|
+
|
|
427
|
+
def execute_dq_rule(
|
|
428
|
+
self,
|
|
429
|
+
rule_id: str | int,
|
|
430
|
+
policy_execution_request: PolicyExecutionRequestInput,
|
|
431
|
+
) -> GenericExecution:
|
|
432
|
+
"""POST ``/rules/data-quality/{rule_id}/executions`` — returns :class:`GenericExecution`."""
|
|
433
|
+
payload = self._payload_from_policy_execution_request(policy_execution_request)
|
|
434
|
+
response = self._client._post(
|
|
435
|
+
url=self._client.catalog_urls.data_quality_rule_executions(rule_id),
|
|
436
|
+
payload=payload,
|
|
437
|
+
)
|
|
438
|
+
logger.debug(f"execute_dq_rule response: {response}")
|
|
439
|
+
return self._parse_generic_execution_response(response, "execute_dq_rule")
|
|
440
|
+
|
|
441
|
+
def execute_reconciliation_rule(
|
|
442
|
+
self,
|
|
443
|
+
rule_id: str | int,
|
|
444
|
+
policy_execution_request: PolicyExecutionRequestInput,
|
|
445
|
+
) -> GenericExecution:
|
|
446
|
+
"""POST ``/rules/reconciliation/{rule_id}/executions`` — returns :class:`GenericExecution`."""
|
|
447
|
+
payload = self._payload_from_policy_execution_request(policy_execution_request)
|
|
448
|
+
response = self._client._post(
|
|
449
|
+
url=self._client.catalog_urls.reconciliation_rule_executions(rule_id),
|
|
450
|
+
payload=payload,
|
|
451
|
+
)
|
|
452
|
+
return self._parse_generic_execution_response(response, "execute_reconciliation_rule")
|
|
453
|
+
|
|
454
|
+
def execute_freshness_rule(
|
|
455
|
+
self,
|
|
456
|
+
rule_id: str | int,
|
|
457
|
+
policy_execution_request: PolicyExecutionRequestInput,
|
|
458
|
+
) -> GenericExecution:
|
|
459
|
+
"""POST ``/rules/data-cadence/{rule_id}/executions`` — returns :class:`GenericExecution`."""
|
|
460
|
+
payload = self._payload_from_policy_execution_request(policy_execution_request)
|
|
461
|
+
response = self._client._post(
|
|
462
|
+
url=self._client.catalog_urls.data_cadence_rule_executions(rule_id),
|
|
463
|
+
payload=payload,
|
|
464
|
+
)
|
|
465
|
+
return self._parse_generic_execution_response(response, "execute_freshness_rule")
|
|
466
|
+
|
|
467
|
+
@staticmethod
|
|
468
|
+
def _payload_from_policy_execution_request(req: PolicyExecutionRequestInput) -> dict[str, Any]:
|
|
469
|
+
api_req = ensure_catalog_policy_execution_request(req)
|
|
470
|
+
if (
|
|
471
|
+
api_req.execution_type == PolicyExecutionType.SELECTIVE.value
|
|
472
|
+
and not api_req.marker_configs
|
|
473
|
+
):
|
|
474
|
+
raise ValueError("markerConfigs is mandatory when execution_type is SELECTIVE")
|
|
475
|
+
# Always use generated model ``to_dict()`` so oneOf wrappers (e.g. MarkerConfig) serialize
|
|
476
|
+
# as plain wire JSON accepted by catalog execution endpoints.
|
|
477
|
+
return api_req.to_dict()
|
|
478
|
+
|
|
479
|
+
@staticmethod
|
|
480
|
+
def _parse_generic_execution_response(response: object, operation_name: str) -> GenericExecution:
|
|
481
|
+
raw = RuleService._require_object_response(response, operation_name)
|
|
482
|
+
first_error: BaseException | None = None
|
|
483
|
+
|
|
484
|
+
model_cls = _EXECUTE_RESPONSE_MODEL.get(operation_name)
|
|
485
|
+
if model_cls is not None and isinstance(raw, dict):
|
|
486
|
+
try:
|
|
487
|
+
typed = model_cls.from_dict(raw)
|
|
488
|
+
if typed is not None:
|
|
489
|
+
logger.debug("%s: parsed as %s", operation_name, model_cls.__name__)
|
|
490
|
+
return GenericExecution(typed)
|
|
491
|
+
except (ValidationError, ValueError, TypeError) as e:
|
|
492
|
+
first_error = e
|
|
493
|
+
logger.debug(
|
|
494
|
+
"%s: %s.from_dict failed; falling back to GenericExecution",
|
|
495
|
+
operation_name,
|
|
496
|
+
model_cls.__name__,
|
|
497
|
+
exc_info=e,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
ge = GenericExecution.from_dict(raw)
|
|
502
|
+
logger.debug("%s: response deserialized as GenericExecution (oneOf)", operation_name)
|
|
503
|
+
return ge
|
|
504
|
+
except (ValueError, ValidationError) as e:
|
|
505
|
+
first_error = e
|
|
506
|
+
logger.debug("%s: %s", operation_name, _describe_generic_execution_parse_failure(e))
|
|
507
|
+
logger.debug("%s: GenericExecution.from_dict failure detail", operation_name, exc_info=e)
|
|
508
|
+
|
|
509
|
+
# Kotlin/Jackson may wrap under a key ending in ``.Execution``.
|
|
510
|
+
for key, value in raw.items():
|
|
511
|
+
if isinstance(value, dict) and key.endswith(".Execution"):
|
|
512
|
+
try:
|
|
513
|
+
ge = GenericExecution.from_dict(value)
|
|
514
|
+
logger.debug("%s: response deserialized from nested key %r", operation_name, key)
|
|
515
|
+
return ge
|
|
516
|
+
except (ValueError, ValidationError):
|
|
517
|
+
continue
|
|
518
|
+
|
|
519
|
+
hint = (
|
|
520
|
+
_describe_generic_execution_parse_failure(first_error)
|
|
521
|
+
if first_error is not None
|
|
522
|
+
else "could not classify parse failure"
|
|
523
|
+
)
|
|
524
|
+
raise TypeError(
|
|
525
|
+
f"{operation_name}: could not deserialize execution response. {hint} "
|
|
526
|
+
f"Top-level keys: {sorted(raw.keys())}."
|
|
527
|
+
) from first_error
|
|
528
|
+
|
|
529
|
+
def get_dq_rule_result(self, execution_id: str | int) -> ExecutionResult:
|
|
530
|
+
"""GET ``/rules/data-quality/executions/{execution_id}/result`` — :class:`ExecutionResult`."""
|
|
531
|
+
url = self._client.catalog_urls.data_quality_rule_execution_result(execution_id)
|
|
532
|
+
response = self._client._get(url=url)
|
|
533
|
+
raw = self._require_object_response(response, "get_dq_rule_result")
|
|
534
|
+
parsed = ExecutionResult.from_dict(raw)
|
|
535
|
+
if parsed is None:
|
|
536
|
+
raise TypeError("get_dq_rule_result received an empty response payload")
|
|
537
|
+
return parsed
|
|
538
|
+
|
|
539
|
+
def get_dq_rule_execution_details(self, execution_id: str | int) -> DataQualityExecution:
|
|
540
|
+
"""GET ``/rules/data-quality/executions/{execution_id}`` — :class:`DataQualityExecution`."""
|
|
541
|
+
url = self._client.catalog_urls.data_quality_rule_execution(execution_id)
|
|
542
|
+
response = self._client._get(url=url)
|
|
543
|
+
raw = self._require_object_response(response, "get_dq_rule_execution_details")
|
|
544
|
+
parsed = DataQualityExecution.from_dict(raw)
|
|
545
|
+
if parsed is None:
|
|
546
|
+
raise TypeError("get_dq_rule_execution_details received an empty response payload")
|
|
547
|
+
return parsed
|
|
548
|
+
|
|
549
|
+
def get_freshness_rule_result(self, execution_id: str | int) -> ExecutionResult:
|
|
550
|
+
"""GET ``/rules/data-cadence/executions/{execution_id}/result`` — :class:`ExecutionResult`."""
|
|
551
|
+
url = self._client.catalog_urls.data_cadence_rule_execution_result(execution_id)
|
|
552
|
+
response = self._client._get(url=url)
|
|
553
|
+
raw = self._require_object_response(response, "get_freshness_rule_result")
|
|
554
|
+
parsed = ExecutionResult.from_dict(raw)
|
|
555
|
+
if parsed is None:
|
|
556
|
+
raise TypeError("get_freshness_rule_result received an empty response payload")
|
|
557
|
+
return parsed
|
|
558
|
+
|
|
559
|
+
def get_data_cadence_rule_execution_details(
|
|
560
|
+
self, execution_id: str | int
|
|
561
|
+
) -> DataCadenceRuleExecution:
|
|
562
|
+
"""GET ``/rules/data-cadence/executions/{execution_id}`` — :class:`DataCadenceRuleExecution`."""
|
|
563
|
+
url = self._client.catalog_urls.data_cadence_rule_execution(execution_id)
|
|
564
|
+
response = self._client._get(url=url)
|
|
565
|
+
raw = self._require_object_response(response, "get_data_cadence_rule_execution_details")
|
|
566
|
+
parsed = DataCadenceRuleExecution.from_dict(raw)
|
|
567
|
+
if parsed is None:
|
|
568
|
+
raise TypeError("get_data_cadence_rule_execution_details received an empty response payload")
|
|
569
|
+
return parsed
|
|
570
|
+
|
|
571
|
+
def cancel_rule_execution(self, execution_id: str | int) -> ExecutionCancellationResponse:
|
|
572
|
+
"""PUT ``/rules/execution/{execution_id}/cancel`` — returns :class:`ExecutionCancellationResponse`."""
|
|
573
|
+
response = self._client._put(
|
|
574
|
+
url=self._client.catalog_urls.cancel_rule_execution(execution_id),
|
|
575
|
+
payload={},
|
|
576
|
+
)
|
|
577
|
+
raw = self._require_object_response(response, "cancel_rule_execution")
|
|
578
|
+
parsed = ExecutionCancellationResponse.from_dict(raw)
|
|
579
|
+
if parsed is None:
|
|
580
|
+
raise TypeError("cancel_rule_execution received an empty response payload")
|
|
581
|
+
if parsed.result.message is None:
|
|
582
|
+
parsed.result.message = (
|
|
583
|
+
f"Cancellation requested for execution {execution_id} "
|
|
584
|
+
f"(status={parsed.result.status})."
|
|
585
|
+
)
|
|
586
|
+
if parsed.result.request_id is None:
|
|
587
|
+
parsed.result.request_id = str(execution_id)
|
|
588
|
+
return parsed
|
|
589
|
+
|
|
590
|
+
def enable_rule(self, rule_id: str | int) -> QualityRule:
|
|
591
|
+
"""PUT ``/rules/{rule_id}/enable`` with :class:`~acceldata.models.api.catalog.enable_rule_request.EnableRuleRequest` (``enable: true``); returns :class:`QualityRule`."""
|
|
592
|
+
payload = EnableRuleRequest(enable=True).to_dict()
|
|
593
|
+
response = self._client._put(
|
|
594
|
+
url=self._client.catalog_urls.rule_enable(rule_id),
|
|
595
|
+
payload=payload,
|
|
596
|
+
)
|
|
597
|
+
raw = self._require_object_response(response, "enable_rule")
|
|
598
|
+
inner = raw.get("rule", raw)
|
|
599
|
+
if not isinstance(inner, dict):
|
|
600
|
+
raise TypeError(f"enable_rule expected rule object, got {type(inner).__name__}")
|
|
601
|
+
parsed = QualityRule.from_dict(inner)
|
|
602
|
+
if parsed is None:
|
|
603
|
+
raise TypeError("enable_rule received an empty QualityRule payload")
|
|
604
|
+
return parsed
|
|
605
|
+
|
|
606
|
+
def disable_rule(self, rule_id: str | int) -> QualityRule:
|
|
607
|
+
"""PUT ``/rules/{rule_id}/enable`` with :class:`~acceldata.models.api.catalog.enable_rule_request.EnableRuleRequest` (``enable: false``); returns :class:`QualityRule`."""
|
|
608
|
+
payload = EnableRuleRequest(enable=False).to_dict()
|
|
609
|
+
response = self._client._put(
|
|
610
|
+
url=self._client.catalog_urls.rule_enable(rule_id),
|
|
611
|
+
payload=payload,
|
|
612
|
+
)
|
|
613
|
+
raw = self._require_object_response(response, "disable_rule")
|
|
614
|
+
inner = raw.get("rule", raw)
|
|
615
|
+
if not isinstance(inner, dict):
|
|
616
|
+
raise TypeError(f"disable_rule expected rule object, got {type(inner).__name__}")
|
|
617
|
+
parsed = QualityRule.from_dict(inner)
|
|
618
|
+
if parsed is None:
|
|
619
|
+
raise TypeError("disable_rule received an empty QualityRule payload")
|
|
620
|
+
return parsed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .catalog_urls import CatalogUrls
|