acryl-datahub-cloud 0.3.11rc0__py3-none-any.whl → 0.3.16.1rc0__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.
Potentially problematic release.
This version of acryl-datahub-cloud might be problematic. Click here for more details.
- acryl_datahub_cloud/_codegen_config.json +1 -1
- acryl_datahub_cloud/acryl_cs_issues/models.py +5 -3
- acryl_datahub_cloud/action_request/action_request_owner_source.py +36 -6
- acryl_datahub_cloud/datahub_forms_notifications/__init__.py +0 -0
- acryl_datahub_cloud/datahub_forms_notifications/forms_notifications_source.py +569 -0
- acryl_datahub_cloud/datahub_forms_notifications/get_feature_flag.gql +7 -0
- acryl_datahub_cloud/datahub_forms_notifications/get_search_results_total.gql +14 -0
- acryl_datahub_cloud/datahub_forms_notifications/query.py +17 -0
- acryl_datahub_cloud/datahub_forms_notifications/scroll_forms_for_notification.gql +29 -0
- acryl_datahub_cloud/datahub_forms_notifications/send_form_notification_request.gql +5 -0
- acryl_datahub_cloud/datahub_reporting/datahub_dataset.py +37 -13
- acryl_datahub_cloud/datahub_reporting/datahub_form_reporting.py +55 -24
- acryl_datahub_cloud/datahub_reporting/extract_graph.py +4 -3
- acryl_datahub_cloud/datahub_reporting/extract_sql.py +242 -51
- acryl_datahub_cloud/datahub_reporting/forms.py +1 -1
- acryl_datahub_cloud/datahub_reporting/forms_config.py +3 -2
- acryl_datahub_cloud/datahub_restore/source.py +3 -2
- acryl_datahub_cloud/datahub_usage_reporting/excluded.py +94 -0
- acryl_datahub_cloud/datahub_usage_reporting/query_builder.py +48 -8
- acryl_datahub_cloud/datahub_usage_reporting/usage_feature_reporter.py +518 -77
- acryl_datahub_cloud/elasticsearch/graph_service.py +76 -14
- acryl_datahub_cloud/graphql_utils.py +64 -0
- acryl_datahub_cloud/lineage_features/source.py +555 -49
- acryl_datahub_cloud/metadata/_urns/urn_defs.py +2296 -1900
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/actionworkflow/__init__.py +53 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/anomaly/__init__.py +2 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/application/__init__.py +19 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/assertion/__init__.py +4 -2
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/common/__init__.py +6 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/conversation/__init__.py +29 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/event/notification/settings/__init__.py +2 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/execution/__init__.py +2 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/file/__init__.py +19 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/form/__init__.py +8 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/identity/__init__.py +8 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/knowledge/__init__.py +33 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/logical/__init__.py +15 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/metadata/key/__init__.py +12 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/metadata/search/features/__init__.py +2 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/module/__init__.py +31 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/notification/__init__.py +19 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/platform/event/v1/__init__.py +4 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/role/__init__.py +2 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/settings/asset/__init__.py +19 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/settings/global/__init__.py +28 -0
- acryl_datahub_cloud/metadata/com/linkedin/pegasus2avro/template/__init__.py +31 -0
- acryl_datahub_cloud/metadata/schema.avsc +25091 -20557
- acryl_datahub_cloud/metadata/schema_classes.py +29269 -23863
- acryl_datahub_cloud/metadata/schemas/ActionRequestInfo.avsc +235 -2
- acryl_datahub_cloud/metadata/schemas/ActionWorkflowInfo.avsc +683 -0
- acryl_datahub_cloud/metadata/schemas/ActionWorkflowKey.avsc +21 -0
- acryl_datahub_cloud/metadata/schemas/Actors.avsc +38 -1
- acryl_datahub_cloud/metadata/schemas/ApplicationKey.avsc +31 -0
- acryl_datahub_cloud/metadata/schemas/ApplicationProperties.avsc +75 -0
- acryl_datahub_cloud/metadata/schemas/Applications.avsc +38 -0
- acryl_datahub_cloud/metadata/schemas/AssertionAnalyticsRunEvent.avsc +353 -215
- acryl_datahub_cloud/metadata/schemas/AssertionInfo.avsc +147 -20
- acryl_datahub_cloud/metadata/schemas/AssertionKey.avsc +1 -1
- acryl_datahub_cloud/metadata/schemas/AssertionRunEvent.avsc +166 -21
- acryl_datahub_cloud/metadata/schemas/{AssertionSummary.avsc → AssertionRunSummary.avsc} +15 -2
- acryl_datahub_cloud/metadata/schemas/AssertionsSummary.avsc +54 -0
- acryl_datahub_cloud/metadata/schemas/AssetSettings.avsc +63 -0
- acryl_datahub_cloud/metadata/schemas/BusinessAttributeInfo.avsc +7 -3
- acryl_datahub_cloud/metadata/schemas/ChartInfo.avsc +20 -6
- acryl_datahub_cloud/metadata/schemas/ChartKey.avsc +1 -0
- acryl_datahub_cloud/metadata/schemas/ConstraintInfo.avsc +12 -1
- acryl_datahub_cloud/metadata/schemas/ContainerKey.avsc +1 -0
- acryl_datahub_cloud/metadata/schemas/ContainerProperties.avsc +16 -5
- acryl_datahub_cloud/metadata/schemas/CorpGroupEditableInfo.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/CorpGroupInfo.avsc +7 -3
- acryl_datahub_cloud/metadata/schemas/CorpGroupKey.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/CorpGroupSettings.avsc +127 -2
- acryl_datahub_cloud/metadata/schemas/CorpUserEditableInfo.avsc +1 -1
- acryl_datahub_cloud/metadata/schemas/CorpUserInfo.avsc +18 -2
- acryl_datahub_cloud/metadata/schemas/CorpUserInvitationStatus.avsc +106 -0
- acryl_datahub_cloud/metadata/schemas/CorpUserKey.avsc +4 -1
- acryl_datahub_cloud/metadata/schemas/CorpUserSettings.avsc +304 -2
- acryl_datahub_cloud/metadata/schemas/CorpUserUsageFeatures.avsc +86 -0
- acryl_datahub_cloud/metadata/schemas/DashboardInfo.avsc +11 -5
- acryl_datahub_cloud/metadata/schemas/DashboardKey.avsc +1 -0
- acryl_datahub_cloud/metadata/schemas/DataFlowInfo.avsc +15 -5
- acryl_datahub_cloud/metadata/schemas/DataFlowKey.avsc +1 -0
- acryl_datahub_cloud/metadata/schemas/DataHubAiConversationInfo.avsc +256 -0
- acryl_datahub_cloud/metadata/schemas/DataHubAiConversationKey.avsc +22 -0
- acryl_datahub_cloud/metadata/schemas/DataHubFileInfo.avsc +234 -0
- acryl_datahub_cloud/metadata/schemas/DataHubFileKey.avsc +22 -0
- acryl_datahub_cloud/metadata/schemas/DataHubIngestionSourceKey.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/DataHubPageModuleKey.avsc +21 -0
- acryl_datahub_cloud/metadata/schemas/DataHubPageModuleProperties.avsc +308 -0
- acryl_datahub_cloud/metadata/schemas/DataHubPageTemplateKey.avsc +21 -0
- acryl_datahub_cloud/metadata/schemas/DataHubPageTemplateProperties.avsc +251 -0
- acryl_datahub_cloud/metadata/schemas/DataHubPolicyInfo.avsc +12 -1
- acryl_datahub_cloud/metadata/schemas/DataJobInfo.avsc +13 -4
- acryl_datahub_cloud/metadata/schemas/DataJobInputOutput.avsc +8 -0
- acryl_datahub_cloud/metadata/schemas/DataJobKey.avsc +1 -0
- acryl_datahub_cloud/metadata/schemas/DataPlatformInfo.avsc +3 -1
- acryl_datahub_cloud/metadata/schemas/DataPlatformInstanceProperties.avsc +5 -2
- acryl_datahub_cloud/metadata/schemas/DataProcessKey.avsc +4 -0
- acryl_datahub_cloud/metadata/schemas/DataProductKey.avsc +2 -0
- acryl_datahub_cloud/metadata/schemas/DataProductProperties.avsc +6 -3
- acryl_datahub_cloud/metadata/schemas/DataTypeInfo.avsc +5 -0
- acryl_datahub_cloud/metadata/schemas/DatasetKey.avsc +10 -2
- acryl_datahub_cloud/metadata/schemas/DatasetProperties.avsc +12 -5
- acryl_datahub_cloud/metadata/schemas/DatasetUsageStatistics.avsc +8 -0
- acryl_datahub_cloud/metadata/schemas/DocumentInfo.avsc +407 -0
- acryl_datahub_cloud/metadata/schemas/DocumentKey.avsc +35 -0
- acryl_datahub_cloud/metadata/schemas/DocumentSettings.avsc +79 -0
- acryl_datahub_cloud/metadata/schemas/DomainKey.avsc +2 -0
- acryl_datahub_cloud/metadata/schemas/DomainProperties.avsc +7 -3
- acryl_datahub_cloud/metadata/schemas/EditableContainerProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableDashboardProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableDataFlowProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableDataJobProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableDatasetProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableERModelRelationshipProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableMLFeatureProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableMLFeatureTableProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableMLModelGroupProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableMLModelProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableNotebookProperties.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/EditableSchemaMetadata.avsc +4 -2
- acryl_datahub_cloud/metadata/schemas/EntityTypeInfo.avsc +5 -0
- acryl_datahub_cloud/metadata/schemas/ExecutionRequestArtifactsLocation.avsc +16 -0
- acryl_datahub_cloud/metadata/schemas/ExecutionRequestKey.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/FormAssignmentStatus.avsc +36 -0
- acryl_datahub_cloud/metadata/schemas/FormInfo.avsc +6 -0
- acryl_datahub_cloud/metadata/schemas/FormKey.avsc +3 -1
- acryl_datahub_cloud/metadata/schemas/FormNotifications.avsc +69 -0
- acryl_datahub_cloud/metadata/schemas/FormSettings.avsc +30 -0
- acryl_datahub_cloud/metadata/schemas/GlobalSettingsInfo.avsc +416 -0
- acryl_datahub_cloud/metadata/schemas/GlobalTags.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/GlossaryNodeInfo.avsc +3 -1
- acryl_datahub_cloud/metadata/schemas/GlossaryNodeKey.avsc +1 -0
- acryl_datahub_cloud/metadata/schemas/GlossaryTermInfo.avsc +3 -1
- acryl_datahub_cloud/metadata/schemas/GlossaryTermKey.avsc +2 -0
- acryl_datahub_cloud/metadata/schemas/IcebergWarehouseInfo.avsc +4 -0
- acryl_datahub_cloud/metadata/schemas/IncidentActivityEvent.avsc +3 -3
- acryl_datahub_cloud/metadata/schemas/IncidentInfo.avsc +3 -3
- acryl_datahub_cloud/metadata/schemas/InferredMetadata.avsc +71 -1
- acryl_datahub_cloud/metadata/schemas/InputFields.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/InviteToken.avsc +26 -0
- acryl_datahub_cloud/metadata/schemas/LineageFeatures.avsc +67 -42
- acryl_datahub_cloud/metadata/schemas/LogicalParent.avsc +145 -0
- acryl_datahub_cloud/metadata/schemas/MLFeatureKey.avsc +4 -1
- acryl_datahub_cloud/metadata/schemas/MLFeatureTableKey.avsc +4 -1
- acryl_datahub_cloud/metadata/schemas/MLModelDeploymentKey.avsc +7 -1
- acryl_datahub_cloud/metadata/schemas/MLModelGroupKey.avsc +9 -1
- acryl_datahub_cloud/metadata/schemas/MLModelKey.avsc +9 -1
- acryl_datahub_cloud/metadata/schemas/MLModelProperties.avsc +4 -2
- acryl_datahub_cloud/metadata/schemas/MLPrimaryKeyKey.avsc +4 -1
- acryl_datahub_cloud/metadata/schemas/MetadataChangeEvent.avsc +418 -97
- acryl_datahub_cloud/metadata/schemas/MetadataChangeLog.avsc +62 -44
- acryl_datahub_cloud/metadata/schemas/MetadataChangeProposal.avsc +61 -0
- acryl_datahub_cloud/metadata/schemas/MonitorAnomalyEvent.avsc +54 -9
- acryl_datahub_cloud/metadata/schemas/MonitorInfo.avsc +163 -23
- acryl_datahub_cloud/metadata/schemas/MonitorKey.avsc +9 -1
- acryl_datahub_cloud/metadata/schemas/MonitorSuiteInfo.avsc +128 -3
- acryl_datahub_cloud/metadata/schemas/NotebookInfo.avsc +5 -2
- acryl_datahub_cloud/metadata/schemas/NotebookKey.avsc +1 -0
- acryl_datahub_cloud/metadata/schemas/NotificationRequest.avsc +91 -4
- acryl_datahub_cloud/metadata/schemas/Operation.avsc +17 -0
- acryl_datahub_cloud/metadata/schemas/Ownership.avsc +71 -1
- acryl_datahub_cloud/metadata/schemas/QuerySubjects.avsc +2 -13
- acryl_datahub_cloud/metadata/schemas/RelationshipChangeEvent.avsc +215 -0
- acryl_datahub_cloud/metadata/schemas/RoleProperties.avsc +3 -1
- acryl_datahub_cloud/metadata/schemas/SchemaFieldInfo.avsc +3 -1
- acryl_datahub_cloud/metadata/schemas/SchemaFieldKey.avsc +3 -0
- acryl_datahub_cloud/metadata/schemas/SchemaMetadata.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/SemanticContent.avsc +123 -0
- acryl_datahub_cloud/metadata/schemas/StructuredProperties.avsc +69 -0
- acryl_datahub_cloud/metadata/schemas/StructuredPropertyDefinition.avsc +15 -4
- acryl_datahub_cloud/metadata/schemas/StructuredPropertySettings.avsc +9 -0
- acryl_datahub_cloud/metadata/schemas/SubscriptionInfo.avsc +136 -5
- acryl_datahub_cloud/metadata/schemas/SubscriptionKey.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/SystemMetadata.avsc +61 -0
- acryl_datahub_cloud/metadata/schemas/TagProperties.avsc +3 -1
- acryl_datahub_cloud/metadata/schemas/TestInfo.avsc +2 -1
- acryl_datahub_cloud/metadata/schemas/UpstreamLineage.avsc +9 -0
- acryl_datahub_cloud/metadata/schemas/UsageFeatures.avsc +10 -0
- acryl_datahub_cloud/notifications/__init__.py +0 -0
- acryl_datahub_cloud/notifications/notification_recipient_builder.py +399 -0
- acryl_datahub_cloud/sdk/__init__.py +69 -0
- acryl_datahub_cloud/sdk/assertion/__init__.py +58 -0
- acryl_datahub_cloud/sdk/assertion/assertion_base.py +779 -0
- acryl_datahub_cloud/sdk/assertion/column_metric_assertion.py +191 -0
- acryl_datahub_cloud/sdk/assertion/column_value_assertion.py +431 -0
- acryl_datahub_cloud/sdk/assertion/freshness_assertion.py +201 -0
- acryl_datahub_cloud/sdk/assertion/schema_assertion.py +268 -0
- acryl_datahub_cloud/sdk/assertion/smart_column_metric_assertion.py +212 -0
- acryl_datahub_cloud/sdk/assertion/smart_freshness_assertion.py +165 -0
- acryl_datahub_cloud/sdk/assertion/smart_sql_assertion.py +156 -0
- acryl_datahub_cloud/sdk/assertion/smart_volume_assertion.py +162 -0
- acryl_datahub_cloud/sdk/assertion/sql_assertion.py +273 -0
- acryl_datahub_cloud/sdk/assertion/types.py +20 -0
- acryl_datahub_cloud/sdk/assertion/volume_assertion.py +156 -0
- acryl_datahub_cloud/sdk/assertion_client/__init__.py +0 -0
- acryl_datahub_cloud/sdk/assertion_client/column_metric.py +545 -0
- acryl_datahub_cloud/sdk/assertion_client/column_value.py +617 -0
- acryl_datahub_cloud/sdk/assertion_client/freshness.py +371 -0
- acryl_datahub_cloud/sdk/assertion_client/helpers.py +166 -0
- acryl_datahub_cloud/sdk/assertion_client/schema.py +358 -0
- acryl_datahub_cloud/sdk/assertion_client/smart_column_metric.py +540 -0
- acryl_datahub_cloud/sdk/assertion_client/smart_freshness.py +373 -0
- acryl_datahub_cloud/sdk/assertion_client/smart_sql.py +411 -0
- acryl_datahub_cloud/sdk/assertion_client/smart_volume.py +380 -0
- acryl_datahub_cloud/sdk/assertion_client/sql.py +410 -0
- acryl_datahub_cloud/sdk/assertion_client/volume.py +446 -0
- acryl_datahub_cloud/sdk/assertion_input/__init__.py +0 -0
- acryl_datahub_cloud/sdk/assertion_input/assertion_input.py +1470 -0
- acryl_datahub_cloud/sdk/assertion_input/column_assertion_constants.py +114 -0
- acryl_datahub_cloud/sdk/assertion_input/column_assertion_utils.py +284 -0
- acryl_datahub_cloud/sdk/assertion_input/column_metric_assertion_input.py +759 -0
- acryl_datahub_cloud/sdk/assertion_input/column_metric_constants.py +109 -0
- acryl_datahub_cloud/sdk/assertion_input/column_value_assertion_input.py +810 -0
- acryl_datahub_cloud/sdk/assertion_input/freshness_assertion_input.py +305 -0
- acryl_datahub_cloud/sdk/assertion_input/schema_assertion_input.py +413 -0
- acryl_datahub_cloud/sdk/assertion_input/smart_column_metric_assertion_input.py +793 -0
- acryl_datahub_cloud/sdk/assertion_input/smart_freshness_assertion_input.py +218 -0
- acryl_datahub_cloud/sdk/assertion_input/smart_sql_assertion_input.py +181 -0
- acryl_datahub_cloud/sdk/assertion_input/smart_volume_assertion_input.py +189 -0
- acryl_datahub_cloud/sdk/assertion_input/sql_assertion_input.py +320 -0
- acryl_datahub_cloud/sdk/assertion_input/volume_assertion_input.py +635 -0
- acryl_datahub_cloud/sdk/assertions_client.py +1074 -0
- acryl_datahub_cloud/sdk/entities/__init__.py +0 -0
- acryl_datahub_cloud/sdk/entities/assertion.py +439 -0
- acryl_datahub_cloud/sdk/entities/monitor.py +291 -0
- acryl_datahub_cloud/sdk/entities/subscription.py +100 -0
- acryl_datahub_cloud/sdk/errors.py +34 -0
- acryl_datahub_cloud/sdk/resolver_client.py +42 -0
- acryl_datahub_cloud/sdk/subscription_client.py +737 -0
- {acryl_datahub_cloud-0.3.11rc0.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/METADATA +55 -49
- {acryl_datahub_cloud-0.3.11rc0.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/RECORD +235 -142
- {acryl_datahub_cloud-0.3.11rc0.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/WHEEL +1 -1
- {acryl_datahub_cloud-0.3.11rc0.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/entry_points.txt +1 -0
- acryl_datahub_cloud/_sdk_extras/__init__.py +0 -4
- acryl_datahub_cloud/_sdk_extras/assertion.py +0 -15
- acryl_datahub_cloud/_sdk_extras/assertions_client.py +0 -23
- {acryl_datahub_cloud-0.3.11rc0.dist-info → acryl_datahub_cloud-0.3.16.1rc0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
from acryl_datahub_cloud.sdk.assertion.assertion_base import (
|
|
8
|
+
AssertionMode,
|
|
9
|
+
_AssertionPublic,
|
|
10
|
+
_HasSchedule,
|
|
11
|
+
)
|
|
12
|
+
from acryl_datahub_cloud.sdk.assertion_input.assertion_input import (
|
|
13
|
+
DEFAULT_DAILY_SCHEDULE,
|
|
14
|
+
DEFAULT_DETECTION_MECHANISM,
|
|
15
|
+
AssertionIncidentBehavior,
|
|
16
|
+
_DetectionMechanismTypes,
|
|
17
|
+
)
|
|
18
|
+
from acryl_datahub_cloud.sdk.assertion_input.sql_assertion_input import (
|
|
19
|
+
SqlAssertionCondition,
|
|
20
|
+
SqlAssertionCriteria,
|
|
21
|
+
)
|
|
22
|
+
from acryl_datahub_cloud.sdk.entities.assertion import Assertion
|
|
23
|
+
from acryl_datahub_cloud.sdk.entities.monitor import Monitor
|
|
24
|
+
from acryl_datahub_cloud.sdk.errors import SDKNotYetSupportedError
|
|
25
|
+
from datahub.metadata import schema_classes as models
|
|
26
|
+
from datahub.metadata.urns import AssertionUrn, CorpUserUrn, DatasetUrn, TagUrn
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SqlAssertion(_AssertionPublic, _HasSchedule):
|
|
32
|
+
"""
|
|
33
|
+
A class that represents a SQL assertion.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
urn: AssertionUrn,
|
|
40
|
+
dataset_urn: DatasetUrn,
|
|
41
|
+
display_name: str,
|
|
42
|
+
mode: AssertionMode,
|
|
43
|
+
statement: str,
|
|
44
|
+
criteria: Optional[SqlAssertionCriteria],
|
|
45
|
+
schedule: models.CronScheduleClass,
|
|
46
|
+
tags: list[TagUrn],
|
|
47
|
+
incident_behavior: list[AssertionIncidentBehavior],
|
|
48
|
+
created_by: Optional[CorpUserUrn] = None,
|
|
49
|
+
created_at: Union[datetime, None] = None,
|
|
50
|
+
updated_by: Optional[CorpUserUrn] = None,
|
|
51
|
+
updated_at: Optional[datetime] = None,
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Initialize a SQL assertion.
|
|
55
|
+
|
|
56
|
+
Note: Values can be accessed, but not set on the assertion object.
|
|
57
|
+
To update an assertion, use the `upsert_*` method.
|
|
58
|
+
Args:
|
|
59
|
+
urn: The urn of the assertion.
|
|
60
|
+
dataset_urn: The urn of the dataset that the assertion is for.
|
|
61
|
+
display_name: The display name of the assertion.
|
|
62
|
+
mode: The mode of the assertion (active, inactive).
|
|
63
|
+
statement: The SQL statement to be used for the assertion.
|
|
64
|
+
criteria: The criteria to be used for the assertion.
|
|
65
|
+
May be None only if backend data is corrupted - this should never happen under normal
|
|
66
|
+
circumstances, but the SDK handles it gracefully to aid debugging.
|
|
67
|
+
schedule: The schedule of the assertion.
|
|
68
|
+
tags: The tags applied to the assertion.
|
|
69
|
+
incident_behavior: Whether to raise or resolve an incident when the assertion fails / passes.
|
|
70
|
+
created_by: The urn of the user that created the assertion.
|
|
71
|
+
created_at: The timestamp of when the assertion was created.
|
|
72
|
+
updated_by: The urn of the user that updated the assertion.
|
|
73
|
+
updated_at: The timestamp of when the assertion was updated.
|
|
74
|
+
"""
|
|
75
|
+
# Initialize the mixins first
|
|
76
|
+
_AssertionPublic.__init__(
|
|
77
|
+
self,
|
|
78
|
+
urn=urn,
|
|
79
|
+
dataset_urn=dataset_urn,
|
|
80
|
+
display_name=display_name,
|
|
81
|
+
mode=mode,
|
|
82
|
+
tags=tags,
|
|
83
|
+
incident_behavior=incident_behavior,
|
|
84
|
+
created_by=created_by,
|
|
85
|
+
created_at=created_at,
|
|
86
|
+
updated_by=updated_by,
|
|
87
|
+
updated_at=updated_at,
|
|
88
|
+
)
|
|
89
|
+
_HasSchedule.__init__(self, schedule=schedule)
|
|
90
|
+
# Then initialize the parent class
|
|
91
|
+
self._statement = statement
|
|
92
|
+
self._criteria = criteria
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def statement(self) -> str:
|
|
96
|
+
return self._statement
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def criteria(self) -> Optional[SqlAssertionCriteria]:
|
|
100
|
+
"""Return the criteria for this assertion, or None if corrupted."""
|
|
101
|
+
return self._criteria
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def criteria_condition(self) -> Union[SqlAssertionCondition, str]:
|
|
105
|
+
if self._criteria is None:
|
|
106
|
+
raise ValueError(
|
|
107
|
+
"Cannot access criteria_condition on corrupted assertion with missing criteria"
|
|
108
|
+
)
|
|
109
|
+
return self._criteria.condition
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def criteria_parameters(
|
|
113
|
+
self,
|
|
114
|
+
) -> Union[Union[float, int], tuple[Union[float, int], Union[float, int]]]:
|
|
115
|
+
if self._criteria is None:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
"Cannot access criteria_parameters on corrupted assertion with missing criteria"
|
|
118
|
+
)
|
|
119
|
+
return self._criteria.parameters
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _get_detection_mechanism(
|
|
123
|
+
assertion: Assertion,
|
|
124
|
+
monitor: Monitor,
|
|
125
|
+
default: Optional[_DetectionMechanismTypes] = DEFAULT_DETECTION_MECHANISM,
|
|
126
|
+
) -> Optional[_DetectionMechanismTypes]:
|
|
127
|
+
"""Sql assertions do not have a detection mechanism."""
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def _get_statement(assertion: Assertion) -> str:
|
|
132
|
+
if assertion.info is None:
|
|
133
|
+
raise SDKNotYetSupportedError(
|
|
134
|
+
f"Assertion {assertion.urn} does not have a SQL assertion info, which is not supported"
|
|
135
|
+
)
|
|
136
|
+
if isinstance(assertion.info, models.SqlAssertionInfoClass):
|
|
137
|
+
return assertion.info.statement
|
|
138
|
+
else:
|
|
139
|
+
raise SDKNotYetSupportedError(
|
|
140
|
+
f"Assertion {assertion.urn} is not a SQL assertion"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _get_condition_from_model_assertion_info(
|
|
145
|
+
assertion_info: models.SqlAssertionInfoClass,
|
|
146
|
+
) -> SqlAssertionCondition:
|
|
147
|
+
"""Convert stored assertion info to condition enum."""
|
|
148
|
+
# Handle value-based conditions (no change type)
|
|
149
|
+
if str(assertion_info.type) == str(models.SqlAssertionTypeClass.METRIC):
|
|
150
|
+
value_conditions = {
|
|
151
|
+
str(
|
|
152
|
+
models.AssertionStdOperatorClass.EQUAL_TO
|
|
153
|
+
): SqlAssertionCondition.IS_EQUAL_TO,
|
|
154
|
+
str(
|
|
155
|
+
models.AssertionStdOperatorClass.NOT_EQUAL_TO
|
|
156
|
+
): SqlAssertionCondition.IS_NOT_EQUAL_TO,
|
|
157
|
+
str(
|
|
158
|
+
models.AssertionStdOperatorClass.GREATER_THAN
|
|
159
|
+
): SqlAssertionCondition.IS_GREATER_THAN,
|
|
160
|
+
str(
|
|
161
|
+
models.AssertionStdOperatorClass.LESS_THAN
|
|
162
|
+
): SqlAssertionCondition.IS_LESS_THAN,
|
|
163
|
+
str(
|
|
164
|
+
models.AssertionStdOperatorClass.BETWEEN
|
|
165
|
+
): SqlAssertionCondition.IS_WITHIN_A_RANGE,
|
|
166
|
+
}
|
|
167
|
+
if str(assertion_info.operator) in value_conditions:
|
|
168
|
+
return value_conditions[str(assertion_info.operator)]
|
|
169
|
+
|
|
170
|
+
# Handle growth-based conditions (with change type)
|
|
171
|
+
elif str(assertion_info.type) == str(
|
|
172
|
+
models.SqlAssertionTypeClass.METRIC_CHANGE
|
|
173
|
+
):
|
|
174
|
+
assert assertion_info.changeType is not None, (
|
|
175
|
+
"changeType must be present for METRIC_CHANGE assertions"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
growth_conditions = {
|
|
179
|
+
(
|
|
180
|
+
str(models.AssertionStdOperatorClass.LESS_THAN_OR_EQUAL_TO),
|
|
181
|
+
str(models.AssertionValueChangeTypeClass.ABSOLUTE),
|
|
182
|
+
): SqlAssertionCondition.GROWS_AT_MOST_ABSOLUTE,
|
|
183
|
+
(
|
|
184
|
+
str(models.AssertionStdOperatorClass.LESS_THAN_OR_EQUAL_TO),
|
|
185
|
+
str(models.AssertionValueChangeTypeClass.PERCENTAGE),
|
|
186
|
+
): SqlAssertionCondition.GROWS_AT_MOST_PERCENTAGE,
|
|
187
|
+
(
|
|
188
|
+
str(models.AssertionStdOperatorClass.GREATER_THAN_OR_EQUAL_TO),
|
|
189
|
+
str(models.AssertionValueChangeTypeClass.ABSOLUTE),
|
|
190
|
+
): SqlAssertionCondition.GROWS_AT_LEAST_ABSOLUTE,
|
|
191
|
+
(
|
|
192
|
+
str(models.AssertionStdOperatorClass.GREATER_THAN_OR_EQUAL_TO),
|
|
193
|
+
str(models.AssertionValueChangeTypeClass.PERCENTAGE),
|
|
194
|
+
): SqlAssertionCondition.GROWS_AT_LEAST_PERCENTAGE,
|
|
195
|
+
(
|
|
196
|
+
str(models.AssertionStdOperatorClass.BETWEEN),
|
|
197
|
+
str(models.AssertionValueChangeTypeClass.ABSOLUTE),
|
|
198
|
+
): SqlAssertionCondition.GROWS_WITHIN_A_RANGE_ABSOLUTE,
|
|
199
|
+
(
|
|
200
|
+
str(models.AssertionStdOperatorClass.BETWEEN),
|
|
201
|
+
str(models.AssertionValueChangeTypeClass.PERCENTAGE),
|
|
202
|
+
): SqlAssertionCondition.GROWS_WITHIN_A_RANGE_PERCENTAGE,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
key = (str(assertion_info.operator), str(assertion_info.changeType))
|
|
206
|
+
if key in growth_conditions:
|
|
207
|
+
return growth_conditions[key]
|
|
208
|
+
|
|
209
|
+
raise ValueError(
|
|
210
|
+
f"Unsupported combination: type={assertion_info.type}, operator={assertion_info.operator}, changeType={assertion_info.changeType}"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def _get_criteria(assertion: Assertion) -> Optional[SqlAssertionCriteria]:
|
|
215
|
+
if assertion.info is None:
|
|
216
|
+
raise SDKNotYetSupportedError(
|
|
217
|
+
f"Assertion {assertion.urn} does not have a SQL assertion info, which is not supported"
|
|
218
|
+
)
|
|
219
|
+
if isinstance(assertion.info, models.SqlAssertionInfoClass):
|
|
220
|
+
# Return None for corrupted assertions with missing parameters or operator
|
|
221
|
+
# This allows validation to happen later with a clearer error message
|
|
222
|
+
if assertion.info.parameters is None or assertion.info.operator is None:
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
parameters: Union[float, tuple[float, float]]
|
|
226
|
+
if assertion.info.parameters.value is not None:
|
|
227
|
+
parameters = float(assertion.info.parameters.value.value)
|
|
228
|
+
elif (
|
|
229
|
+
assertion.info.parameters.maxValue is not None
|
|
230
|
+
and assertion.info.parameters.minValue is not None
|
|
231
|
+
):
|
|
232
|
+
# min and max values are in the order of min, max
|
|
233
|
+
parameters = (
|
|
234
|
+
float(assertion.info.parameters.minValue.value),
|
|
235
|
+
float(assertion.info.parameters.maxValue.value),
|
|
236
|
+
)
|
|
237
|
+
else:
|
|
238
|
+
# Parameters field exists but has no valid values - corrupted data
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
condition = SqlAssertion._get_condition_from_model_assertion_info(
|
|
242
|
+
assertion.info
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return SqlAssertionCriteria(
|
|
246
|
+
condition=condition,
|
|
247
|
+
parameters=parameters,
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
raise SDKNotYetSupportedError(
|
|
251
|
+
f"Assertion {assertion.urn} is not a SQL assertion"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def _from_entities(cls, assertion: Assertion, monitor: Monitor) -> Self:
|
|
256
|
+
"""
|
|
257
|
+
Create a SQL assertion from the assertion and monitor entities.
|
|
258
|
+
"""
|
|
259
|
+
return cls(
|
|
260
|
+
urn=assertion.urn,
|
|
261
|
+
dataset_urn=assertion.dataset,
|
|
262
|
+
display_name=assertion.description or "",
|
|
263
|
+
mode=cls._get_mode(monitor),
|
|
264
|
+
statement=cls._get_statement(assertion),
|
|
265
|
+
criteria=cls._get_criteria(assertion),
|
|
266
|
+
schedule=cls._get_schedule(monitor, default=DEFAULT_DAILY_SCHEDULE),
|
|
267
|
+
tags=cls._get_tags(assertion),
|
|
268
|
+
incident_behavior=cls._get_incident_behavior(assertion),
|
|
269
|
+
created_by=cls._get_created_by(assertion),
|
|
270
|
+
created_at=cls._get_created_at(assertion),
|
|
271
|
+
updated_by=cls._get_updated_by(assertion),
|
|
272
|
+
updated_at=cls._get_updated_at(assertion),
|
|
273
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from acryl_datahub_cloud.sdk.assertion.assertion_base import (
|
|
4
|
+
FreshnessAssertion,
|
|
5
|
+
SmartFreshnessAssertion,
|
|
6
|
+
SmartVolumeAssertion,
|
|
7
|
+
SqlAssertion,
|
|
8
|
+
)
|
|
9
|
+
from acryl_datahub_cloud.sdk.assertion.smart_column_metric_assertion import (
|
|
10
|
+
SmartColumnMetricAssertion,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
AssertionTypes = Union[
|
|
14
|
+
SmartFreshnessAssertion,
|
|
15
|
+
SmartVolumeAssertion,
|
|
16
|
+
FreshnessAssertion,
|
|
17
|
+
SmartColumnMetricAssertion,
|
|
18
|
+
SqlAssertion,
|
|
19
|
+
# TODO: Add other assertion types here as we add them.
|
|
20
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional, Union
|
|
4
|
+
|
|
5
|
+
from typing_extensions import Self
|
|
6
|
+
|
|
7
|
+
from acryl_datahub_cloud.sdk.assertion.assertion_base import (
|
|
8
|
+
AssertionMode,
|
|
9
|
+
_AssertionPublic,
|
|
10
|
+
_HasSchedule,
|
|
11
|
+
)
|
|
12
|
+
from acryl_datahub_cloud.sdk.assertion_input.assertion_input import (
|
|
13
|
+
DEFAULT_DETECTION_MECHANISM,
|
|
14
|
+
AssertionIncidentBehavior,
|
|
15
|
+
DetectionMechanism,
|
|
16
|
+
_DetectionMechanismTypes,
|
|
17
|
+
)
|
|
18
|
+
from acryl_datahub_cloud.sdk.assertion_input.volume_assertion_input import (
|
|
19
|
+
VolumeAssertionCriteria,
|
|
20
|
+
)
|
|
21
|
+
from acryl_datahub_cloud.sdk.entities.assertion import Assertion
|
|
22
|
+
from acryl_datahub_cloud.sdk.entities.monitor import Monitor
|
|
23
|
+
from acryl_datahub_cloud.sdk.errors import SDKNotYetSupportedError
|
|
24
|
+
from datahub.metadata import schema_classes as models
|
|
25
|
+
from datahub.metadata.urns import AssertionUrn, CorpUserUrn, DatasetUrn, TagUrn
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class VolumeAssertion(_HasSchedule, _AssertionPublic):
|
|
31
|
+
"""
|
|
32
|
+
A class that represents a volume assertion.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
*,
|
|
38
|
+
urn: AssertionUrn,
|
|
39
|
+
dataset_urn: DatasetUrn,
|
|
40
|
+
display_name: str,
|
|
41
|
+
mode: AssertionMode,
|
|
42
|
+
schedule: models.CronScheduleClass,
|
|
43
|
+
criteria: VolumeAssertionCriteria,
|
|
44
|
+
tags: list[TagUrn],
|
|
45
|
+
incident_behavior: list[AssertionIncidentBehavior],
|
|
46
|
+
detection_mechanism: Optional[
|
|
47
|
+
_DetectionMechanismTypes
|
|
48
|
+
] = DEFAULT_DETECTION_MECHANISM,
|
|
49
|
+
created_by: Optional[CorpUserUrn] = None,
|
|
50
|
+
created_at: Union[datetime, None] = None,
|
|
51
|
+
updated_by: Optional[CorpUserUrn] = None,
|
|
52
|
+
updated_at: Optional[datetime] = None,
|
|
53
|
+
):
|
|
54
|
+
"""
|
|
55
|
+
Initialize a volume assertion.
|
|
56
|
+
|
|
57
|
+
Note: Values can be accessed, but not set on the assertion object.
|
|
58
|
+
To update an assertion, use the `upsert_*` method.
|
|
59
|
+
Args:
|
|
60
|
+
urn: The urn of the assertion.
|
|
61
|
+
dataset_urn: The urn of the dataset that the assertion is for.
|
|
62
|
+
display_name: The display name of the assertion.
|
|
63
|
+
mode: The mode of the assertion (active, inactive).
|
|
64
|
+
schedule: The schedule of the assertion.
|
|
65
|
+
criteria: The volume assertion criteria.
|
|
66
|
+
tags: The tags applied to the assertion.
|
|
67
|
+
incident_behavior: Whether to raise or resolve an incident when the assertion fails / passes.
|
|
68
|
+
detection_mechanism: The detection mechanism of the assertion.
|
|
69
|
+
created_by: The urn of the user that created the assertion.
|
|
70
|
+
created_at: The timestamp of when the assertion was created.
|
|
71
|
+
updated_by: The urn of the user that updated the assertion.
|
|
72
|
+
updated_at: The timestamp of when the assertion was updated.
|
|
73
|
+
"""
|
|
74
|
+
_HasSchedule.__init__(self, schedule=schedule)
|
|
75
|
+
_AssertionPublic.__init__(
|
|
76
|
+
self,
|
|
77
|
+
urn=urn,
|
|
78
|
+
dataset_urn=dataset_urn,
|
|
79
|
+
display_name=display_name,
|
|
80
|
+
mode=mode,
|
|
81
|
+
incident_behavior=incident_behavior,
|
|
82
|
+
detection_mechanism=detection_mechanism,
|
|
83
|
+
created_by=created_by,
|
|
84
|
+
created_at=created_at,
|
|
85
|
+
updated_by=updated_by,
|
|
86
|
+
updated_at=updated_at,
|
|
87
|
+
tags=tags,
|
|
88
|
+
)
|
|
89
|
+
self._criteria = criteria
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def criteria(self) -> VolumeAssertionCriteria:
|
|
93
|
+
return self._criteria
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _get_volume_definition(
|
|
97
|
+
assertion: Assertion,
|
|
98
|
+
) -> VolumeAssertionCriteria:
|
|
99
|
+
"""Get volume assertion definition from a DataHub assertion entity."""
|
|
100
|
+
return VolumeAssertionCriteria.from_assertion(assertion)
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _get_detection_mechanism(
|
|
104
|
+
assertion: Assertion,
|
|
105
|
+
monitor: Monitor,
|
|
106
|
+
default: Optional[_DetectionMechanismTypes] = DEFAULT_DETECTION_MECHANISM,
|
|
107
|
+
) -> Optional[_DetectionMechanismTypes]:
|
|
108
|
+
"""Get the detection mechanism for volume assertions."""
|
|
109
|
+
parameters = _AssertionPublic._get_validated_detection_context(
|
|
110
|
+
monitor,
|
|
111
|
+
assertion,
|
|
112
|
+
models.AssertionEvaluationParametersTypeClass.DATASET_VOLUME,
|
|
113
|
+
models.VolumeAssertionInfoClass,
|
|
114
|
+
default,
|
|
115
|
+
)
|
|
116
|
+
if parameters is None:
|
|
117
|
+
return default
|
|
118
|
+
if parameters.datasetVolumeParameters is None:
|
|
119
|
+
logger.warning(
|
|
120
|
+
f"Monitor does not have datasetVolumeParameters, defaulting detection mechanism to {DEFAULT_DETECTION_MECHANISM}"
|
|
121
|
+
)
|
|
122
|
+
if default is None:
|
|
123
|
+
return DEFAULT_DETECTION_MECHANISM
|
|
124
|
+
else:
|
|
125
|
+
return default
|
|
126
|
+
source_type = parameters.datasetVolumeParameters.sourceType
|
|
127
|
+
if source_type == models.DatasetVolumeSourceTypeClass.INFORMATION_SCHEMA:
|
|
128
|
+
return DetectionMechanism.INFORMATION_SCHEMA
|
|
129
|
+
elif source_type == models.DatasetVolumeSourceTypeClass.QUERY:
|
|
130
|
+
additional_filter = _AssertionPublic._get_additional_filter(assertion)
|
|
131
|
+
return DetectionMechanism.QUERY(additional_filter=additional_filter)
|
|
132
|
+
elif source_type == models.DatasetVolumeSourceTypeClass.DATAHUB_DATASET_PROFILE:
|
|
133
|
+
return DetectionMechanism.DATASET_PROFILE
|
|
134
|
+
else:
|
|
135
|
+
raise SDKNotYetSupportedError(f"DatasetVolumeSourceType {source_type}")
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def _from_entities(cls, assertion: Assertion, monitor: Monitor) -> Self:
|
|
139
|
+
"""
|
|
140
|
+
Create a volume assertion from the assertion and monitor entities.
|
|
141
|
+
"""
|
|
142
|
+
return cls(
|
|
143
|
+
urn=assertion.urn,
|
|
144
|
+
dataset_urn=assertion.dataset,
|
|
145
|
+
display_name=assertion.description or "",
|
|
146
|
+
mode=cls._get_mode(monitor),
|
|
147
|
+
schedule=cls._get_schedule(monitor),
|
|
148
|
+
criteria=cls._get_volume_definition(assertion),
|
|
149
|
+
incident_behavior=cls._get_incident_behavior(assertion),
|
|
150
|
+
detection_mechanism=cls._get_detection_mechanism(assertion, monitor),
|
|
151
|
+
created_by=cls._get_created_by(assertion),
|
|
152
|
+
created_at=cls._get_created_at(assertion),
|
|
153
|
+
updated_by=cls._get_updated_by(assertion),
|
|
154
|
+
updated_at=cls._get_updated_at(assertion),
|
|
155
|
+
tags=cls._get_tags(assertion),
|
|
156
|
+
)
|
|
File without changes
|