acryl-datahub-cloud 0.3.12rc6__py3-none-any.whl → 0.3.12rc7__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.

@@ -10,6 +10,7 @@ from acryl_datahub_cloud.sdk.assertion.assertion_base import (
10
10
  SmartFreshnessAssertion,
11
11
  SmartVolumeAssertion,
12
12
  SqlAssertion,
13
+ VolumeAssertion,
13
14
  _AssertionPublic,
14
15
  )
15
16
  from acryl_datahub_cloud.sdk.assertion.smart_column_metric_assertion import (
@@ -41,6 +42,14 @@ from acryl_datahub_cloud.sdk.assertion_input.sql_assertion_input import (
41
42
  SqlAssertionCriteria,
42
43
  _SqlAssertionInput,
43
44
  )
45
+ from acryl_datahub_cloud.sdk.assertion_input.volume_assertion_input import (
46
+ RowCountTotal,
47
+ VolumeAssertionDefinition,
48
+ VolumeAssertionDefinitionInputTypes,
49
+ VolumeAssertionOperator,
50
+ _VolumeAssertionDefinitionTypes,
51
+ _VolumeAssertionInput,
52
+ )
44
53
  from acryl_datahub_cloud.sdk.entities.assertion import Assertion, TagsInputType
45
54
  from acryl_datahub_cloud.sdk.entities.monitor import Monitor
46
55
  from acryl_datahub_cloud.sdk.errors import SDKUsageError
@@ -494,6 +503,113 @@ class AssertionsClient:
494
503
 
495
504
  return merged_assertion_input
496
505
 
506
+ def _retrieve_and_merge_native_volume_assertion_and_monitor(
507
+ self,
508
+ assertion_input: _VolumeAssertionInput,
509
+ dataset_urn: Union[str, DatasetUrn],
510
+ urn: Union[str, AssertionUrn],
511
+ display_name: Optional[str],
512
+ enabled: Optional[bool],
513
+ detection_mechanism: DetectionMechanismInputTypes,
514
+ incident_behavior: Optional[
515
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
516
+ ],
517
+ tags: Optional[TagsInputType],
518
+ updated_by: Optional[Union[str, CorpUserUrn]],
519
+ now_utc: datetime,
520
+ schedule: Optional[Union[str, models.CronScheduleClass]],
521
+ definition: Optional[VolumeAssertionDefinitionInputTypes],
522
+ use_backend_definition: bool = False,
523
+ ) -> Union[VolumeAssertion, _VolumeAssertionInput]:
524
+ # 1. Retrieve any existing assertion and monitor entities:
525
+ maybe_assertion_entity, monitor_urn, maybe_monitor_entity = (
526
+ self._retrieve_assertion_and_monitor(assertion_input)
527
+ )
528
+
529
+ # 2.1 If the assertion and monitor entities exist, create an assertion object from them:
530
+ if maybe_assertion_entity and maybe_monitor_entity:
531
+ existing_assertion = VolumeAssertion._from_entities(
532
+ maybe_assertion_entity, maybe_monitor_entity
533
+ )
534
+ # 2.2 If the assertion exists but the monitor does not, create a placeholder monitor entity to be able to create the assertion:
535
+ elif maybe_assertion_entity and not maybe_monitor_entity:
536
+ monitor_mode = (
537
+ "ACTIVE" if enabled else "INACTIVE" if enabled is not None else "ACTIVE"
538
+ )
539
+ existing_assertion = VolumeAssertion._from_entities(
540
+ maybe_assertion_entity,
541
+ Monitor(id=monitor_urn, info=("ASSERTION", monitor_mode)),
542
+ )
543
+ # 2.3 If the assertion does not exist, create a new assertion with a generated urn and return the assertion input:
544
+ elif not maybe_assertion_entity:
545
+ if use_backend_definition:
546
+ raise SDKUsageError(
547
+ f"Cannot sync assertion {urn}: no existing definition found in backend and no definition provided in request"
548
+ )
549
+ logger.info(
550
+ f"No existing assertion entity found for assertion urn {urn}, creating a new assertion with a generated urn"
551
+ )
552
+ return self._create_volume_assertion(
553
+ dataset_urn=dataset_urn,
554
+ display_name=display_name,
555
+ detection_mechanism=detection_mechanism,
556
+ incident_behavior=incident_behavior,
557
+ tags=tags,
558
+ created_by=updated_by,
559
+ schedule=schedule,
560
+ definition=definition,
561
+ )
562
+
563
+ # 3. Check for any issues e.g. different dataset urns
564
+ if (
565
+ existing_assertion
566
+ and hasattr(existing_assertion, "dataset_urn")
567
+ and existing_assertion.dataset_urn != assertion_input.dataset_urn
568
+ ):
569
+ raise SDKUsageError(
570
+ f"Dataset URN mismatch, existing assertion: {existing_assertion.dataset_urn} != new assertion: {dataset_urn}"
571
+ )
572
+
573
+ # 4. Handle definition: use backend definition if flag is set and backend has one
574
+ if use_backend_definition:
575
+ if maybe_assertion_entity is not None:
576
+ # Use definition from backend
577
+ backend_definition = VolumeAssertionDefinition.from_assertion(
578
+ maybe_assertion_entity
579
+ )
580
+ # Update the assertion_input with the real definition from backend
581
+ assertion_input.definition = backend_definition
582
+ effective_definition = backend_definition
583
+ logger.info("Using definition from backend assertion")
584
+ else:
585
+ # No backend assertion and no user-provided definition - this is an error
586
+ raise SDKUsageError(
587
+ f"Cannot sync assertion {urn}: no existing definition found in backend and no definition provided in request"
588
+ )
589
+ else:
590
+ # Use the already-parsed definition from assertion_input
591
+ effective_definition = assertion_input.definition
592
+
593
+ # 5. Merge the existing assertion with the validated input:
594
+ merged_assertion_input = self._merge_volume_input(
595
+ dataset_urn=dataset_urn,
596
+ urn=urn,
597
+ display_name=display_name,
598
+ enabled=enabled,
599
+ detection_mechanism=detection_mechanism,
600
+ incident_behavior=incident_behavior,
601
+ tags=tags,
602
+ now_utc=now_utc,
603
+ assertion_input=assertion_input,
604
+ maybe_assertion_entity=maybe_assertion_entity,
605
+ maybe_monitor_entity=maybe_monitor_entity,
606
+ existing_assertion=existing_assertion,
607
+ schedule=schedule,
608
+ definition=effective_definition,
609
+ )
610
+
611
+ return merged_assertion_input
612
+
497
613
  def _retrieve_and_merge_sql_assertion_and_monitor(
498
614
  self,
499
615
  assertion_input: _SqlAssertionInput,
@@ -562,8 +678,6 @@ class AssertionsClient:
562
678
  urn=urn,
563
679
  display_name=display_name,
564
680
  enabled=enabled,
565
- criteria=criteria,
566
- statement=statement,
567
681
  incident_behavior=incident_behavior,
568
682
  tags=tags,
569
683
  now_utc=now_utc,
@@ -571,6 +685,8 @@ class AssertionsClient:
571
685
  maybe_assertion_entity=maybe_assertion_entity,
572
686
  existing_assertion=existing_assertion,
573
687
  schedule=schedule,
688
+ criteria=criteria,
689
+ statement=statement,
574
690
  )
575
691
 
576
692
  return merged_assertion_input
@@ -867,6 +983,116 @@ class AssertionsClient:
867
983
  )
868
984
  return merged_assertion_input
869
985
 
986
+ def _merge_volume_input(
987
+ self,
988
+ dataset_urn: Union[str, DatasetUrn],
989
+ urn: Union[str, AssertionUrn],
990
+ display_name: Optional[str],
991
+ enabled: Optional[bool],
992
+ detection_mechanism: DetectionMechanismInputTypes,
993
+ incident_behavior: Optional[
994
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
995
+ ],
996
+ tags: Optional[TagsInputType],
997
+ now_utc: datetime,
998
+ assertion_input: _VolumeAssertionInput,
999
+ maybe_assertion_entity: Optional[Assertion],
1000
+ maybe_monitor_entity: Optional[Monitor],
1001
+ existing_assertion: VolumeAssertion,
1002
+ schedule: Optional[Union[str, models.CronScheduleClass]],
1003
+ definition: Optional[_VolumeAssertionDefinitionTypes],
1004
+ ) -> _VolumeAssertionInput:
1005
+ """Merge the input with the existing assertion and monitor entities.
1006
+
1007
+ Args:
1008
+ dataset_urn: The urn of the dataset to be monitored.
1009
+ urn: The urn of the assertion.
1010
+ display_name: The display name of the assertion.
1011
+ enabled: Whether the assertion is enabled.
1012
+ detection_mechanism: The detection mechanism to be used for the assertion.
1013
+ incident_behavior: The incident behavior to be applied to the assertion.
1014
+ tags: The tags to be applied to the assertion.
1015
+ now_utc: The current UTC time from when the function is called.
1016
+ assertion_input: The validated input to the function.
1017
+ maybe_assertion_entity: The existing assertion entity from the DataHub instance.
1018
+ maybe_monitor_entity: The existing monitor entity from the DataHub instance.
1019
+ existing_assertion: The existing assertion from the DataHub instance.
1020
+ schedule: The schedule to be applied to the assertion.
1021
+ definition: The volume assertion definition to be applied to the assertion.
1022
+
1023
+ Returns:
1024
+ The merged assertion input.
1025
+ """
1026
+ merged_assertion_input = _VolumeAssertionInput(
1027
+ urn=urn,
1028
+ entity_client=self.client.entities,
1029
+ dataset_urn=dataset_urn,
1030
+ display_name=_merge_field(
1031
+ display_name,
1032
+ "display_name",
1033
+ assertion_input,
1034
+ existing_assertion,
1035
+ maybe_assertion_entity.description if maybe_assertion_entity else None,
1036
+ ),
1037
+ enabled=_merge_field(
1038
+ enabled,
1039
+ "enabled",
1040
+ assertion_input,
1041
+ existing_assertion,
1042
+ existing_assertion.mode == AssertionMode.ACTIVE
1043
+ if existing_assertion
1044
+ else None,
1045
+ ),
1046
+ schedule=_merge_field(
1047
+ schedule,
1048
+ "schedule",
1049
+ assertion_input,
1050
+ existing_assertion,
1051
+ existing_assertion.schedule if existing_assertion else None,
1052
+ ),
1053
+ detection_mechanism=_merge_field(
1054
+ detection_mechanism,
1055
+ "detection_mechanism",
1056
+ assertion_input,
1057
+ existing_assertion,
1058
+ VolumeAssertion._get_detection_mechanism(
1059
+ maybe_assertion_entity, maybe_monitor_entity, default=None
1060
+ )
1061
+ if maybe_assertion_entity and maybe_monitor_entity
1062
+ else None,
1063
+ ),
1064
+ incident_behavior=_merge_field(
1065
+ incident_behavior,
1066
+ "incident_behavior",
1067
+ assertion_input,
1068
+ existing_assertion,
1069
+ VolumeAssertion._get_incident_behavior(maybe_assertion_entity)
1070
+ if maybe_assertion_entity
1071
+ else None,
1072
+ ),
1073
+ tags=_merge_field(
1074
+ tags,
1075
+ "tags",
1076
+ assertion_input,
1077
+ existing_assertion,
1078
+ maybe_assertion_entity.tags if maybe_assertion_entity else None,
1079
+ ),
1080
+ definition=_merge_field(
1081
+ definition,
1082
+ "definition",
1083
+ assertion_input,
1084
+ existing_assertion,
1085
+ existing_assertion.definition if existing_assertion else None,
1086
+ ),
1087
+ created_by=existing_assertion.created_by
1088
+ or DEFAULT_CREATED_BY, # Override with the existing assertion's created_by or the default created_by if not set
1089
+ created_at=existing_assertion.created_at
1090
+ or now_utc, # Override with the existing assertion's created_at or now if not set
1091
+ updated_by=assertion_input.updated_by, # Override with the input's updated_by
1092
+ updated_at=assertion_input.updated_at, # Override with the input's updated_at (now)
1093
+ )
1094
+ return merged_assertion_input
1095
+
870
1096
  def _merge_sql_input(
871
1097
  self,
872
1098
  dataset_urn: Union[str, DatasetUrn],
@@ -1444,6 +1670,119 @@ class AssertionsClient:
1444
1670
  # raise e
1445
1671
  return FreshnessAssertion._from_entities(assertion_entity, monitor_entity)
1446
1672
 
1673
+ def _create_volume_assertion(
1674
+ self,
1675
+ *,
1676
+ dataset_urn: Union[str, DatasetUrn],
1677
+ display_name: Optional[str] = None,
1678
+ enabled: bool = True,
1679
+ detection_mechanism: DetectionMechanismInputTypes = None,
1680
+ incident_behavior: Optional[
1681
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
1682
+ ] = None,
1683
+ tags: Optional[TagsInputType] = None,
1684
+ created_by: Optional[Union[str, CorpUserUrn]] = None,
1685
+ schedule: Optional[Union[str, models.CronScheduleClass]] = None,
1686
+ definition: Optional[VolumeAssertionDefinitionInputTypes] = None,
1687
+ ) -> VolumeAssertion:
1688
+ """Create a volume assertion.
1689
+
1690
+ Note: keyword arguments are required.
1691
+
1692
+ The created assertion will use the default daily schedule ("0 0 * * *").
1693
+
1694
+ Args:
1695
+ dataset_urn: The urn of the dataset to be monitored.
1696
+ display_name: The display name of the assertion. If not provided, a random display
1697
+ name will be generated.
1698
+ enabled: Whether the assertion is enabled. Defaults to True.
1699
+ detection_mechanism: The detection mechanism to be used for the assertion. Information
1700
+ schema is recommended. Valid values are:
1701
+ - "information_schema" or DetectionMechanism.INFORMATION_SCHEMA
1702
+ - "audit_log" or DetectionMechanism.AUDIT_LOG
1703
+ - {
1704
+ "type": "last_modified_column",
1705
+ "column_name": "last_modified",
1706
+ "additional_filter": "last_modified > '2021-01-01'",
1707
+ } or DetectionMechanism.LAST_MODIFIED_COLUMN(column_name='last_modified',
1708
+ additional_filter='last_modified > 2021-01-01')
1709
+ - "datahub_operation" or DetectionMechanism.DATAHUB_OPERATION
1710
+ incident_behavior: The incident behavior to be applied to the assertion. Valid values are:
1711
+ - "raise_on_fail" or AssertionIncidentBehavior.RAISE_ON_FAIL
1712
+ - "resolve_on_pass" or AssertionIncidentBehavior.RESOLVE_ON_PASS
1713
+ tags: The tags to be applied to the assertion. Valid values are:
1714
+ - a list of strings (strings will be converted to TagUrn objects)
1715
+ - a list of TagUrn objects
1716
+ - a list of TagAssociationClass objects
1717
+ created_by: Optional urn of the user who created the assertion. The format is
1718
+ "urn:li:corpuser:<username>", which you can find on the Users & Groups page.
1719
+ The default is the datahub system user.
1720
+ TODO: Retrieve the SDK user as the default instead of the datahub system user.
1721
+ schedule: Optional cron formatted schedule for the assertion. If not provided, a default
1722
+ schedule will be used. The schedule determines when the assertion will be evaluated.
1723
+ The format is a cron expression, e.g. "0 * * * *" for every hour using UTC timezone.
1724
+ Alternatively, a models.CronScheduleClass object can be provided with string parameters
1725
+ cron and timezone. Use `from datahub.metadata import schema_classes as models` to import the class.
1726
+ definition: The volume assertion definition. Must be provided and include type, operator,
1727
+ and parameters. Can be provided as:
1728
+ - A typed volume assertion object (RowCountTotal or RowCountChange)
1729
+ - A dictionary with keys: type, operator, parameters (and kind for row_count_change)
1730
+
1731
+ Example dictionary for row count total:
1732
+ {
1733
+ "type": "row_count_total",
1734
+ "operator": "GREATER_THAN_OR_EQUAL_TO",
1735
+ "parameters": 100
1736
+ }
1737
+
1738
+ Example dictionary for row count change:
1739
+ {
1740
+ "type": "row_count_change",
1741
+ "kind": "percent",
1742
+ "operator": "BETWEEN",
1743
+ "parameters": (10, 50)
1744
+ }
1745
+
1746
+ Returns:
1747
+ VolumeAssertion: The created assertion.
1748
+ """
1749
+ _print_experimental_warning()
1750
+ now_utc = datetime.now(timezone.utc)
1751
+ if created_by is None:
1752
+ logger.warning(
1753
+ f"Created by is not set, using {DEFAULT_CREATED_BY} as a placeholder"
1754
+ )
1755
+ created_by = DEFAULT_CREATED_BY
1756
+ assertion_input = _VolumeAssertionInput(
1757
+ urn=None,
1758
+ entity_client=self.client.entities,
1759
+ dataset_urn=dataset_urn,
1760
+ display_name=display_name,
1761
+ enabled=enabled,
1762
+ detection_mechanism=detection_mechanism,
1763
+ incident_behavior=incident_behavior,
1764
+ tags=tags,
1765
+ created_by=created_by,
1766
+ created_at=now_utc,
1767
+ updated_by=created_by,
1768
+ updated_at=now_utc,
1769
+ schedule=schedule,
1770
+ definition=definition,
1771
+ )
1772
+ assertion_entity, monitor_entity = (
1773
+ assertion_input.to_assertion_and_monitor_entities()
1774
+ )
1775
+ # If assertion creation fails, we won't try to create the monitor
1776
+ self.client.entities.create(assertion_entity)
1777
+ # TODO: Wrap monitor creation in a try-except and delete the assertion if monitor creation fails (once delete is implemented https://linear.app/acryl-data/issue/OBS-1350/add-delete-method-to-entity-clientpy)
1778
+ # try:
1779
+ self.client.entities.create(monitor_entity)
1780
+ # except Exception as e:
1781
+ # logger.error(f"Error creating monitor: {e}")
1782
+ # self.client.entities.delete(assertion_entity)
1783
+ # raise e
1784
+ return VolumeAssertion._from_entities(assertion_entity, monitor_entity)
1785
+
1447
1786
  def _create_sql_assertion(
1448
1787
  self,
1449
1788
  *,
@@ -2606,6 +2945,194 @@ class AssertionsClient:
2606
2945
 
2607
2946
  return FreshnessAssertion._from_entities(assertion_entity, monitor_entity)
2608
2947
 
2948
+ def sync_volume_assertion(
2949
+ self,
2950
+ *,
2951
+ dataset_urn: Union[str, DatasetUrn],
2952
+ urn: Optional[Union[str, AssertionUrn]] = None,
2953
+ display_name: Optional[str] = None,
2954
+ enabled: Optional[bool] = None,
2955
+ detection_mechanism: DetectionMechanismInputTypes = None,
2956
+ incident_behavior: Optional[
2957
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
2958
+ ] = None,
2959
+ tags: Optional[TagsInputType] = None,
2960
+ updated_by: Optional[Union[str, CorpUserUrn]] = None,
2961
+ schedule: Optional[Union[str, models.CronScheduleClass]] = None,
2962
+ definition: Optional[VolumeAssertionDefinitionInputTypes] = None,
2963
+ ) -> VolumeAssertion:
2964
+ """Upsert and merge a volume assertion.
2965
+
2966
+ Note: keyword arguments are required.
2967
+
2968
+ Upsert and merge is a combination of create and update. If the assertion does not exist,
2969
+ it will be created. If it does exist, it will be updated. Existing assertion fields will
2970
+ be updated if the input value is not None. If the input value is None, the existing value
2971
+ will be preserved. If the input value can be un-set e.g. by passing an empty list or
2972
+ empty string.
2973
+
2974
+ Schedule behavior:
2975
+ - Create case: Uses default daily schedule ("0 0 * * *") or provided schedule
2976
+ - Update case: Uses existing schedule or provided schedule.
2977
+
2978
+ Args:
2979
+ dataset_urn: The urn of the dataset to be monitored.
2980
+ urn: The urn of the assertion. If not provided, a urn will be generated and the assertion
2981
+ will be _created_ in the DataHub instance.
2982
+ display_name: The display name of the assertion. If not provided, a random display name
2983
+ will be generated.
2984
+ enabled: Whether the assertion is enabled. If not provided, the existing value
2985
+ will be preserved.
2986
+ detection_mechanism: The detection mechanism to be used for the assertion. Information
2987
+ schema is recommended. Valid values are:
2988
+ - "information_schema" or DetectionMechanism.INFORMATION_SCHEMA
2989
+ - "audit_log" or DetectionMechanism.AUDIT_LOG
2990
+ - {
2991
+ "type": "last_modified_column",
2992
+ "column_name": "last_modified",
2993
+ "additional_filter": "last_modified > '2021-01-01'",
2994
+ } or DetectionMechanism.LAST_MODIFIED_COLUMN(column_name='last_modified',
2995
+ additional_filter='last_modified > 2021-01-01')
2996
+ - "datahub_operation" or DetectionMechanism.DATAHUB_OPERATION
2997
+ incident_behavior: The incident behavior to be applied to the assertion. Valid values are:
2998
+ - "raise_on_fail" or AssertionIncidentBehavior.RAISE_ON_FAIL
2999
+ - "resolve_on_pass" or AssertionIncidentBehavior.RESOLVE_ON_PASS
3000
+ tags: The tags to be applied to the assertion. Valid values are:
3001
+ - a list of strings (strings will be converted to TagUrn objects)
3002
+ - a list of TagUrn objects
3003
+ - a list of TagAssociationClass objects
3004
+ updated_by: Optional urn of the user who updated the assertion. The format is
3005
+ "urn:li:corpuser:<username>", which you can find on the Users & Groups page.
3006
+ The default is the datahub system user.
3007
+ TODO: Retrieve the SDK user as the default instead of the datahub system user.
3008
+ schedule: Optional cron formatted schedule for the assertion. If not provided, a default
3009
+ schedule will be used. The schedule determines when the assertion will be evaluated.
3010
+ The format is a cron expression, e.g. "0 * * * *" for every hour using UTC timezone.
3011
+ Alternatively, a models.CronScheduleClass object can be provided with string parameters
3012
+ cron and timezone. Use `from datahub.metadata import schema_classes as models` to import the class.
3013
+ definition: The volume assertion definition. Can be provided as:
3014
+ - A typed volume assertion object (RowCountTotal or RowCountChange)
3015
+ - A dictionary with keys: type, operator, parameters (and kind for row_count_change)
3016
+ - None to preserve the existing definition from the backend (for update operations)
3017
+
3018
+ Example dictionary for row count total:
3019
+ {
3020
+ "type": "row_count_total",
3021
+ "operator": "GREATER_THAN_OR_EQUAL_TO",
3022
+ "parameters": 100
3023
+ }
3024
+
3025
+ Example dictionary for row count change:
3026
+ {
3027
+ "type": "row_count_change",
3028
+ "kind": "absolute",
3029
+ "operator": "LESS_THAN_OR_EQUAL_TO",
3030
+ "parameters": 50
3031
+ }
3032
+
3033
+ Returns:
3034
+ VolumeAssertion: The created or updated assertion.
3035
+ """
3036
+ _print_experimental_warning()
3037
+ now_utc = datetime.now(timezone.utc)
3038
+
3039
+ if updated_by is None:
3040
+ logger.warning(
3041
+ f"updated_by is not set, using {DEFAULT_CREATED_BY} as a placeholder"
3042
+ )
3043
+ updated_by = DEFAULT_CREATED_BY
3044
+
3045
+ # 1. If urn is not set, create a new assertion
3046
+ if urn is None:
3047
+ logger.info("URN is not set, creating a new assertion")
3048
+ return self._create_volume_assertion(
3049
+ dataset_urn=dataset_urn,
3050
+ display_name=display_name,
3051
+ enabled=enabled if enabled is not None else True,
3052
+ detection_mechanism=detection_mechanism,
3053
+ incident_behavior=incident_behavior,
3054
+ tags=tags,
3055
+ created_by=updated_by,
3056
+ schedule=schedule,
3057
+ definition=definition,
3058
+ )
3059
+
3060
+ # 2. If urn is set, prepare definition for validation
3061
+ # We use temporary default definition if None is provided, just to pass the _VolumeAssertionInput validation.
3062
+ # However, we keep memory of this in use_backend_definition flag, so we can later
3063
+ # fail if there is no definition in backend (basically, there is no assertion). That would mean that
3064
+ # this is a creation case and the user missed the definition parameter, which is required.
3065
+ # Likely this pattern never happened before because there is no a publicly documented default definition
3066
+ # that we can use as fallback.
3067
+ use_backend_definition = definition is None
3068
+ temp_definition = (
3069
+ definition
3070
+ if definition is not None
3071
+ else RowCountTotal(
3072
+ operator=VolumeAssertionOperator.GREATER_THAN_OR_EQUAL_TO,
3073
+ parameters=0, # Temporary placeholder
3074
+ )
3075
+ )
3076
+
3077
+ # 3. Create assertion input with effective definition
3078
+ assertion_input = _VolumeAssertionInput(
3079
+ urn=urn,
3080
+ dataset_urn=dataset_urn,
3081
+ entity_client=self.client.entities,
3082
+ detection_mechanism=detection_mechanism,
3083
+ incident_behavior=incident_behavior,
3084
+ tags=tags,
3085
+ created_by=updated_by, # This will be overridden by the actual created_by
3086
+ created_at=now_utc, # This will be overridden by the actual created_at
3087
+ updated_by=updated_by,
3088
+ updated_at=now_utc,
3089
+ schedule=schedule,
3090
+ definition=temp_definition,
3091
+ )
3092
+
3093
+ # 4. Merge the assertion input with the existing assertion and monitor entities or create a new assertion
3094
+ # if the assertion does not exist:
3095
+ merged_assertion_input_or_created_assertion = (
3096
+ self._retrieve_and_merge_native_volume_assertion_and_monitor(
3097
+ assertion_input=assertion_input,
3098
+ dataset_urn=dataset_urn,
3099
+ urn=urn,
3100
+ display_name=display_name,
3101
+ enabled=enabled,
3102
+ detection_mechanism=detection_mechanism,
3103
+ definition=definition,
3104
+ use_backend_definition=use_backend_definition,
3105
+ incident_behavior=incident_behavior,
3106
+ tags=tags,
3107
+ updated_by=updated_by,
3108
+ now_utc=now_utc,
3109
+ schedule=schedule,
3110
+ )
3111
+ )
3112
+
3113
+ # Return early if we created a new assertion in the merge:
3114
+ if isinstance(merged_assertion_input_or_created_assertion, _AssertionPublic):
3115
+ # We know this is the correct type because we passed the assertion_class parameter
3116
+ assert isinstance(
3117
+ merged_assertion_input_or_created_assertion, VolumeAssertion
3118
+ )
3119
+ return merged_assertion_input_or_created_assertion
3120
+
3121
+ # 4. Upsert the assertion and monitor entities:
3122
+ assertion_entity, monitor_entity = (
3123
+ merged_assertion_input_or_created_assertion.to_assertion_and_monitor_entities()
3124
+ )
3125
+ # If assertion upsert fails, we won't try to upsert the monitor
3126
+ self.client.entities.upsert(assertion_entity)
3127
+ # TODO: Wrap monitor upsert in a try-except and delete the assertion if monitor upsert fails (once delete is implemented https://linear.app/acryl-data/issue/OBS-1350/add-delete-method-to-entity-clientpy)
3128
+ # try:
3129
+ self.client.entities.upsert(monitor_entity)
3130
+ # except Exception as e:
3131
+ # logger.error(f"Error upserting monitor: {e}")
3132
+ # self.client.entities.delete(assertion_entity)
3133
+ # raise e
3134
+ return VolumeAssertion._from_entities(assertion_entity, monitor_entity)
3135
+
2609
3136
  def sync_sql_assertion(
2610
3137
  self,
2611
3138
  *,