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

@@ -6,19 +6,24 @@ from typing import TYPE_CHECKING, Any, Optional, Union
6
6
 
7
7
  from acryl_datahub_cloud.sdk.assertion import (
8
8
  AssertionMode,
9
+ FreshnessAssertion,
9
10
  SmartFreshnessAssertion,
10
11
  SmartVolumeAssertion,
11
12
  _AssertionPublic,
12
13
  )
13
- from acryl_datahub_cloud.sdk.assertion_input import (
14
+ from acryl_datahub_cloud.sdk.assertion_input.assertion_input import (
14
15
  AssertionIncidentBehavior,
15
16
  DetectionMechanismInputTypes,
16
17
  ExclusionWindowInputTypes,
17
18
  InferenceSensitivity,
19
+ TimeWindowSizeInputTypes,
18
20
  _AssertionInput,
19
21
  _SmartFreshnessAssertionInput,
20
22
  _SmartVolumeAssertionInput,
21
23
  )
24
+ from acryl_datahub_cloud.sdk.assertion_input.freshness_assertion_input import (
25
+ _FreshnessAssertionInput,
26
+ )
22
27
  from acryl_datahub_cloud.sdk.entities.assertion import Assertion, TagsInputType
23
28
  from acryl_datahub_cloud.sdk.entities.monitor import Monitor
24
29
  from acryl_datahub_cloud.sdk.errors import SDKUsageError
@@ -171,7 +176,7 @@ class AssertionsClient:
171
176
  # 3. Merge the assertion input with the existing assertion and monitor entities or create a new assertion
172
177
  # if the assertion does not exist:
173
178
  merged_assertion_input_or_created_assertion = (
174
- self._retrieve_and_merge_freshness_assertion_and_monitor(
179
+ self._retrieve_and_merge_smart_freshness_assertion_and_monitor(
175
180
  assertion_input=assertion_input,
176
181
  dataset_urn=dataset_urn,
177
182
  urn=urn,
@@ -212,7 +217,7 @@ class AssertionsClient:
212
217
 
213
218
  return SmartFreshnessAssertion._from_entities(assertion_entity, monitor_entity)
214
219
 
215
- def _retrieve_and_merge_freshness_assertion_and_monitor(
220
+ def _retrieve_and_merge_smart_freshness_assertion_and_monitor(
216
221
  self,
217
222
  assertion_input: _SmartFreshnessAssertionInput,
218
223
  dataset_urn: Union[str, DatasetUrn],
@@ -277,7 +282,7 @@ class AssertionsClient:
277
282
  )
278
283
 
279
284
  # 4. Merge the existing assertion with the validated input:
280
- merged_assertion_input = self._merge_freshness_input(
285
+ merged_assertion_input = self._merge_smart_freshness_input(
281
286
  dataset_urn=dataset_urn,
282
287
  urn=urn,
283
288
  display_name=display_name,
@@ -350,6 +355,7 @@ class AssertionsClient:
350
355
  incident_behavior=incident_behavior,
351
356
  tags=tags,
352
357
  created_by=updated_by,
358
+ schedule=schedule,
353
359
  )
354
360
 
355
361
  # 3. Check for any issues e.g. different dataset urns
@@ -363,7 +369,7 @@ class AssertionsClient:
363
369
  )
364
370
 
365
371
  # 4. Merge the existing assertion with the validated input:
366
- merged_assertion_input = self._merge_volume_input(
372
+ merged_assertion_input = self._merge_smart_volume_input(
367
373
  dataset_urn=dataset_urn,
368
374
  urn=urn,
369
375
  display_name=display_name,
@@ -384,6 +390,93 @@ class AssertionsClient:
384
390
 
385
391
  return merged_assertion_input
386
392
 
393
+ def _retrieve_and_merge_freshness_assertion_and_monitor(
394
+ self,
395
+ assertion_input: _FreshnessAssertionInput,
396
+ dataset_urn: Union[str, DatasetUrn],
397
+ urn: Union[str, AssertionUrn],
398
+ display_name: Optional[str],
399
+ enabled: Optional[bool],
400
+ detection_mechanism: DetectionMechanismInputTypes,
401
+ incident_behavior: Optional[
402
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
403
+ ],
404
+ tags: Optional[TagsInputType],
405
+ updated_by: Optional[Union[str, CorpUserUrn]],
406
+ now_utc: datetime,
407
+ schedule: Optional[Union[str, models.CronScheduleClass]],
408
+ freshness_schedule_check_type: Optional[
409
+ Union[str, models.FreshnessAssertionScheduleTypeClass]
410
+ ] = None,
411
+ lookback_window: Optional[TimeWindowSizeInputTypes] = None,
412
+ ) -> Union[FreshnessAssertion, _FreshnessAssertionInput]:
413
+ # 1. Retrieve any existing assertion and monitor entities:
414
+ maybe_assertion_entity, monitor_urn, maybe_monitor_entity = (
415
+ self._retrieve_assertion_and_monitor(assertion_input)
416
+ )
417
+
418
+ # 2.1 If the assertion and monitor entities exist, create an assertion object from them:
419
+ if maybe_assertion_entity and maybe_monitor_entity:
420
+ existing_assertion = FreshnessAssertion._from_entities(
421
+ maybe_assertion_entity, maybe_monitor_entity
422
+ )
423
+ # 2.2 If the assertion exists but the monitor does not, create a placeholder monitor entity to be able to create the assertion:
424
+ elif maybe_assertion_entity and not maybe_monitor_entity:
425
+ monitor_mode = (
426
+ "ACTIVE" if enabled else "INACTIVE" if enabled is not None else "ACTIVE"
427
+ )
428
+ existing_assertion = FreshnessAssertion._from_entities(
429
+ maybe_assertion_entity,
430
+ Monitor(id=monitor_urn, info=("ASSERTION", monitor_mode)),
431
+ )
432
+ # 2.3 If the assertion does not exist, create a new assertion with a generated urn and return the assertion input:
433
+ elif not maybe_assertion_entity:
434
+ logger.info(
435
+ f"No existing assertion entity found for assertion urn {urn}, creating a new assertion with a generated urn"
436
+ )
437
+ return self._create_freshness_assertion(
438
+ dataset_urn=dataset_urn,
439
+ display_name=display_name,
440
+ detection_mechanism=detection_mechanism,
441
+ incident_behavior=incident_behavior,
442
+ tags=tags,
443
+ created_by=updated_by,
444
+ schedule=schedule,
445
+ freshness_schedule_check_type=freshness_schedule_check_type,
446
+ lookback_window=lookback_window,
447
+ )
448
+
449
+ # 3. Check for any issues e.g. different dataset urns
450
+ if (
451
+ existing_assertion
452
+ and hasattr(existing_assertion, "dataset_urn")
453
+ and existing_assertion.dataset_urn != assertion_input.dataset_urn
454
+ ):
455
+ raise SDKUsageError(
456
+ f"Dataset URN mismatch, existing assertion: {existing_assertion.dataset_urn} != new assertion: {dataset_urn}"
457
+ )
458
+
459
+ # 4. Merge the existing assertion with the validated input:
460
+ merged_assertion_input = self._merge_freshness_input(
461
+ dataset_urn=dataset_urn,
462
+ urn=urn,
463
+ display_name=display_name,
464
+ enabled=enabled,
465
+ detection_mechanism=detection_mechanism,
466
+ incident_behavior=incident_behavior,
467
+ tags=tags,
468
+ now_utc=now_utc,
469
+ assertion_input=assertion_input,
470
+ maybe_assertion_entity=maybe_assertion_entity,
471
+ maybe_monitor_entity=maybe_monitor_entity,
472
+ existing_assertion=existing_assertion,
473
+ schedule=schedule,
474
+ freshness_schedule_check_type=freshness_schedule_check_type,
475
+ lookback_window=lookback_window,
476
+ )
477
+
478
+ return merged_assertion_input
479
+
387
480
  def _retrieve_assertion_and_monitor(
388
481
  self,
389
482
  assertion_input: _AssertionInput,
@@ -423,7 +516,7 @@ class AssertionsClient:
423
516
 
424
517
  return maybe_assertion_entity, monitor_urn, maybe_monitor_entity
425
518
 
426
- def _merge_freshness_input(
519
+ def _merge_smart_freshness_input(
427
520
  self,
428
521
  dataset_urn: Union[str, DatasetUrn],
429
522
  urn: Union[str, AssertionUrn],
@@ -554,7 +647,129 @@ class AssertionsClient:
554
647
 
555
648
  return merged_assertion_input
556
649
 
557
- def _merge_volume_input(
650
+ def _merge_freshness_input(
651
+ self,
652
+ dataset_urn: Union[str, DatasetUrn],
653
+ urn: Union[str, AssertionUrn],
654
+ display_name: Optional[str],
655
+ enabled: Optional[bool],
656
+ detection_mechanism: DetectionMechanismInputTypes,
657
+ incident_behavior: Optional[
658
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
659
+ ],
660
+ tags: Optional[TagsInputType],
661
+ now_utc: datetime,
662
+ assertion_input: _FreshnessAssertionInput,
663
+ maybe_assertion_entity: Optional[Assertion],
664
+ maybe_monitor_entity: Optional[Monitor],
665
+ existing_assertion: FreshnessAssertion,
666
+ schedule: Optional[Union[str, models.CronScheduleClass]],
667
+ freshness_schedule_check_type: Optional[
668
+ Union[str, models.FreshnessAssertionScheduleTypeClass]
669
+ ] = None,
670
+ lookback_window: Optional[TimeWindowSizeInputTypes] = None,
671
+ ) -> _FreshnessAssertionInput:
672
+ """Merge the input with the existing assertion and monitor entities.
673
+
674
+ Args:
675
+ dataset_urn: The urn of the dataset to be monitored.
676
+ urn: The urn of the assertion.
677
+ display_name: The display name of the assertion.
678
+ enabled: Whether the assertion is enabled.
679
+ incident_behavior: The incident behavior to be applied to the assertion.
680
+ tags: The tags to be applied to the assertion.
681
+ now_utc: The current UTC time from when the function is called.
682
+ assertion_input: The validated input to the function.
683
+ maybe_assertion_entity: The existing assertion entity from the DataHub instance.
684
+ maybe_monitor_entity: The existing monitor entity from the DataHub instance.
685
+ existing_assertion: The existing assertion from the DataHub instance.
686
+ schedule: The schedule to be applied to the assertion.
687
+ freshness_schedule_check_type: The freshness schedule check type to be applied to the assertion.
688
+ lookback_window: The lookback window to be applied to the assertion.
689
+
690
+ Returns:
691
+ The merged assertion input.
692
+ """
693
+ merged_assertion_input = _FreshnessAssertionInput(
694
+ urn=urn,
695
+ entity_client=self.client.entities,
696
+ dataset_urn=dataset_urn,
697
+ display_name=_merge_field(
698
+ display_name,
699
+ "display_name",
700
+ assertion_input,
701
+ existing_assertion,
702
+ maybe_assertion_entity.description if maybe_assertion_entity else None,
703
+ ),
704
+ enabled=_merge_field(
705
+ enabled,
706
+ "enabled",
707
+ assertion_input,
708
+ existing_assertion,
709
+ existing_assertion.mode == AssertionMode.ACTIVE
710
+ if existing_assertion
711
+ else None,
712
+ ),
713
+ schedule=_merge_field(
714
+ schedule,
715
+ "schedule",
716
+ assertion_input,
717
+ existing_assertion,
718
+ existing_assertion.schedule if existing_assertion else None,
719
+ ),
720
+ freshness_schedule_check_type=_merge_field(
721
+ freshness_schedule_check_type,
722
+ "freshness_schedule_check_type",
723
+ assertion_input,
724
+ existing_assertion,
725
+ existing_assertion._freshness_schedule_check_type
726
+ if existing_assertion
727
+ else None,
728
+ ),
729
+ lookback_window=_merge_field(
730
+ lookback_window,
731
+ "lookback_window",
732
+ assertion_input,
733
+ existing_assertion,
734
+ existing_assertion.lookback_window if existing_assertion else None,
735
+ ),
736
+ detection_mechanism=_merge_field(
737
+ detection_mechanism,
738
+ "detection_mechanism",
739
+ assertion_input,
740
+ existing_assertion,
741
+ FreshnessAssertion._get_detection_mechanism(
742
+ maybe_assertion_entity, maybe_monitor_entity, default=None
743
+ )
744
+ if maybe_assertion_entity and maybe_monitor_entity
745
+ else None,
746
+ ),
747
+ incident_behavior=_merge_field(
748
+ incident_behavior,
749
+ "incident_behavior",
750
+ assertion_input,
751
+ existing_assertion,
752
+ FreshnessAssertion._get_incident_behavior(maybe_assertion_entity)
753
+ if maybe_assertion_entity
754
+ else None,
755
+ ),
756
+ tags=_merge_field(
757
+ tags,
758
+ "tags",
759
+ assertion_input,
760
+ existing_assertion,
761
+ maybe_assertion_entity.tags if maybe_assertion_entity else None,
762
+ ),
763
+ created_by=existing_assertion.created_by
764
+ or DEFAULT_CREATED_BY, # Override with the existing assertion's created_by or the default created_by if not set
765
+ created_at=existing_assertion.created_at
766
+ or now_utc, # Override with the existing assertion's created_at or now if not set
767
+ updated_by=assertion_input.updated_by, # Override with the input's updated_by
768
+ updated_at=assertion_input.updated_at, # Override with the input's updated_at (now)
769
+ )
770
+ return merged_assertion_input
771
+
772
+ def _merge_smart_volume_input(
558
773
  self,
559
774
  dataset_urn: Union[str, DatasetUrn],
560
775
  urn: Union[str, AssertionUrn],
@@ -919,6 +1134,112 @@ class AssertionsClient:
919
1134
  # raise e
920
1135
  return SmartVolumeAssertion._from_entities(assertion_entity, monitor_entity)
921
1136
 
1137
+ def _create_freshness_assertion(
1138
+ self,
1139
+ *,
1140
+ dataset_urn: Union[str, DatasetUrn],
1141
+ display_name: Optional[str] = None,
1142
+ enabled: bool = True,
1143
+ freshness_schedule_check_type: Optional[
1144
+ Union[str, models.FreshnessAssertionScheduleTypeClass]
1145
+ ] = None,
1146
+ lookback_window: Optional[TimeWindowSizeInputTypes] = None,
1147
+ detection_mechanism: DetectionMechanismInputTypes = None,
1148
+ incident_behavior: Optional[
1149
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
1150
+ ] = None,
1151
+ tags: Optional[TagsInputType] = None,
1152
+ created_by: Optional[Union[str, CorpUserUrn]] = None,
1153
+ schedule: Optional[Union[str, models.CronScheduleClass]] = None,
1154
+ ) -> FreshnessAssertion:
1155
+ """Create a freshness assertion.
1156
+
1157
+ Note: keyword arguments are required.
1158
+
1159
+ The created assertion will use the default daily schedule ("0 0 * * *").
1160
+
1161
+ Args:
1162
+ dataset_urn: The urn of the dataset to be monitored.
1163
+ display_name: The display name of the assertion. If not provided, a random display
1164
+ name will be generated.
1165
+ enabled: Whether the assertion is enabled. Defaults to True.
1166
+ detection_mechanism: The detection mechanism to be used for the assertion. Information
1167
+ schema is recommended. Valid values are:
1168
+ - "information_schema" or DetectionMechanism.INFORMATION_SCHEMA
1169
+ - "audit_log" or DetectionMechanism.AUDIT_LOG
1170
+ - {
1171
+ "type": "last_modified_column",
1172
+ "column_name": "last_modified",
1173
+ "additional_filter": "last_modified > '2021-01-01'",
1174
+ } or DetectionMechanism.LAST_MODIFIED_COLUMN(column_name='last_modified',
1175
+ additional_filter='last_modified > 2021-01-01')
1176
+ - "datahub_operation" or DetectionMechanism.DATAHUB_OPERATION
1177
+ freshness_schedule_check_type: The freshness schedule check type to be applied to the assertion. Valid values are:
1178
+ - "since_the_last_check" or models.FreshnessAssertionScheduleTypeClass.SINCE_THE_LAST_CHECK
1179
+ - "cron" or models.FreshnessAssertionScheduleTypeClass.CRON
1180
+ lookback_window: The lookback window to be applied to the assertion. Valid values are:
1181
+ - from models.TimeWindowSize objects: models.TimeWindowSizeClass(
1182
+ unit=models.CalendarIntervalClass.DAY,
1183
+ multiple=1)
1184
+ - from TimeWindowSize objects: TimeWindowSize(unit='DAY', multiple=1)
1185
+ incident_behavior: The incident behavior to be applied to the assertion. Valid values are:
1186
+ - "raise_on_fail" or AssertionIncidentBehavior.RAISE_ON_FAIL
1187
+ - "resolve_on_pass" or AssertionIncidentBehavior.RESOLVE_ON_PASS
1188
+ tags: The tags to be applied to the assertion. Valid values are:
1189
+ - a list of strings (strings will be converted to TagUrn objects)
1190
+ - a list of TagUrn objects
1191
+ - a list of TagAssociationClass objects
1192
+ created_by: Optional urn of the user who created the assertion. The format is
1193
+ "urn:li:corpuser:<username>", which you can find on the Users & Groups page.
1194
+ The default is the datahub system user.
1195
+ TODO: Retrieve the SDK user as the default instead of the datahub system user.
1196
+ schedule: Optional cron formatted schedule for the assertion. If not provided, a default
1197
+ schedule will be used. The schedule determines when the assertion will be evaluated.
1198
+ The format is a cron expression, e.g. "0 * * * *" for every hour using UTC timezone.
1199
+ Alternatively, a models.CronScheduleClass object can be provided with string parameters
1200
+ cron and timezone. Use `from datahub.metadata import schema_classes as models` to import the class.
1201
+
1202
+ Returns:
1203
+ FreshnessAssertion: The created assertion.
1204
+ """
1205
+ _print_experimental_warning()
1206
+ now_utc = datetime.now(timezone.utc)
1207
+ if created_by is None:
1208
+ logger.warning(
1209
+ f"Created by is not set, using {DEFAULT_CREATED_BY} as a placeholder"
1210
+ )
1211
+ created_by = DEFAULT_CREATED_BY
1212
+ assertion_input = _FreshnessAssertionInput(
1213
+ urn=None,
1214
+ entity_client=self.client.entities,
1215
+ dataset_urn=dataset_urn,
1216
+ display_name=display_name,
1217
+ enabled=enabled,
1218
+ detection_mechanism=detection_mechanism,
1219
+ freshness_schedule_check_type=freshness_schedule_check_type,
1220
+ lookback_window=lookback_window,
1221
+ incident_behavior=incident_behavior,
1222
+ tags=tags,
1223
+ created_by=created_by,
1224
+ created_at=now_utc,
1225
+ updated_by=created_by,
1226
+ updated_at=now_utc,
1227
+ schedule=schedule,
1228
+ )
1229
+ assertion_entity, monitor_entity = (
1230
+ assertion_input.to_assertion_and_monitor_entities()
1231
+ )
1232
+ # If assertion creation fails, we won't try to create the monitor
1233
+ self.client.entities.create(assertion_entity)
1234
+ # 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)
1235
+ # try:
1236
+ self.client.entities.create(monitor_entity)
1237
+ # except Exception as e:
1238
+ # logger.error(f"Error creating monitor: {e}")
1239
+ # self.client.entities.delete(assertion_entity)
1240
+ # raise e
1241
+ return FreshnessAssertion._from_entities(assertion_entity, monitor_entity)
1242
+
922
1243
  def sync_smart_volume_assertion(
923
1244
  self,
924
1245
  *,
@@ -1106,6 +1427,171 @@ class AssertionsClient:
1106
1427
 
1107
1428
  return SmartVolumeAssertion._from_entities(assertion_entity, monitor_entity)
1108
1429
 
1430
+ def sync_freshness_assertion(
1431
+ self,
1432
+ *,
1433
+ dataset_urn: Union[str, DatasetUrn],
1434
+ urn: Optional[Union[str, AssertionUrn]] = None,
1435
+ display_name: Optional[str] = None,
1436
+ enabled: Optional[bool] = None,
1437
+ detection_mechanism: DetectionMechanismInputTypes = None,
1438
+ incident_behavior: Optional[
1439
+ Union[AssertionIncidentBehavior, list[AssertionIncidentBehavior]]
1440
+ ] = None,
1441
+ tags: Optional[TagsInputType] = None,
1442
+ updated_by: Optional[Union[str, CorpUserUrn]] = None,
1443
+ freshness_schedule_check_type: Optional[
1444
+ Union[str, models.FreshnessAssertionScheduleTypeClass]
1445
+ ] = None,
1446
+ schedule: Optional[Union[str, models.CronScheduleClass]] = None,
1447
+ lookback_window: Optional[TimeWindowSizeInputTypes] = None,
1448
+ ) -> FreshnessAssertion:
1449
+ """Upsert and merge a freshness assertion.
1450
+
1451
+ Note: keyword arguments are required.
1452
+
1453
+ Upsert and merge is a combination of create and update. If the assertion does not exist,
1454
+ it will be created. If it does exist, it will be updated. Existing assertion fields will
1455
+ be updated if the input value is not None. If the input value is None, the existing value
1456
+ will be preserved. If the input value can be un-set e.g. by passing an empty list or
1457
+ empty string.
1458
+
1459
+ Schedule behavior:
1460
+ - Create case: Uses default daily schedule (\"0 0 * * *\") or provided schedule
1461
+ - Update case: Uses existing schedule or provided schedule.
1462
+
1463
+ Args:
1464
+ dataset_urn: The urn of the dataset to be monitored.
1465
+ urn: The urn of the assertion. If not provided, a urn will be generated and the assertion
1466
+ will be _created_ in the DataHub instance.
1467
+ display_name: The display name of the assertion. If not provided, a random display name
1468
+ will be generated.
1469
+ enabled: Whether the assertion is enabled. If not provided, the existing value
1470
+ will be preserved.
1471
+ detection_mechanism: The detection mechanism to be used for the assertion. Information
1472
+ schema is recommended. Valid values are:
1473
+ - "information_schema" or DetectionMechanism.INFORMATION_SCHEMA
1474
+ - "audit_log" or DetectionMechanism.AUDIT_LOG
1475
+ - {
1476
+ "type": "last_modified_column",
1477
+ "column_name": "last_modified",
1478
+ "additional_filter": "last_modified > '2021-01-01'",
1479
+ } or DetectionMechanism.LAST_MODIFIED_COLUMN(column_name='last_modified',
1480
+ additional_filter='last_modified > 2021-01-01')
1481
+ - {
1482
+ "type": "high_watermark_column",
1483
+ "column_name": "id",
1484
+ "additional_filter": "id > 1000",
1485
+ } or DetectionMechanism.HIGH_WATERMARK_COLUMN(column_name='id',
1486
+ additional_filter='id > 1000')
1487
+ - "datahub_operation" or DetectionMechanism.DATAHUB_OPERATION
1488
+ incident_behavior: The incident behavior to be applied to the assertion. Valid values are:
1489
+ - "raise_on_fail" or AssertionIncidentBehavior.RAISE_ON_FAIL
1490
+ - "resolve_on_pass" or AssertionIncidentBehavior.RESOLVE_ON_PASS
1491
+ tags: The tags to be applied to the assertion. Valid values are:
1492
+ - a list of strings (strings will be converted to TagUrn objects)
1493
+ - a list of TagUrn objects
1494
+ - a list of TagAssociationClass objects
1495
+ updated_by: Optional urn of the user who updated the assertion. The format is
1496
+ "urn:li:corpuser:<username>", which you can find on the Users & Groups page.
1497
+ The default is the datahub system user.
1498
+ TODO: Retrieve the SDK user as the default instead of the datahub system user.
1499
+ schedule: Optional cron formatted schedule for the assertion. If not provided, a default
1500
+ schedule will be used. The schedule determines when the assertion will be evaluated.
1501
+ The format is a cron expression, e.g. "0 * * * *" for every hour using UTC timezone.
1502
+ Alternatively, a models.CronScheduleClass object can be provided with string parameters
1503
+ cron and timezone. Use `from datahub.metadata import schema_classes as models` to import the class.
1504
+
1505
+ Returns:
1506
+ FreshnessAssertion: The created or updated assertion.
1507
+ """
1508
+ _print_experimental_warning()
1509
+ now_utc = datetime.now(timezone.utc)
1510
+
1511
+ if updated_by is None:
1512
+ logger.warning(
1513
+ f"updated_by is not set, using {DEFAULT_CREATED_BY} as a placeholder"
1514
+ )
1515
+ updated_by = DEFAULT_CREATED_BY
1516
+
1517
+ # 1. If urn is not set, create a new assertion
1518
+ if urn is None:
1519
+ logger.info("URN is not set, creating a new assertion")
1520
+ return self._create_freshness_assertion(
1521
+ dataset_urn=dataset_urn,
1522
+ display_name=display_name,
1523
+ enabled=enabled if enabled is not None else True,
1524
+ detection_mechanism=detection_mechanism,
1525
+ incident_behavior=incident_behavior,
1526
+ tags=tags,
1527
+ created_by=updated_by,
1528
+ schedule=schedule,
1529
+ freshness_schedule_check_type=freshness_schedule_check_type,
1530
+ lookback_window=lookback_window,
1531
+ )
1532
+
1533
+ # 2. If urn is set, first validate the input:
1534
+ assertion_input = _FreshnessAssertionInput(
1535
+ urn=urn,
1536
+ entity_client=self.client.entities,
1537
+ dataset_urn=dataset_urn,
1538
+ display_name=display_name,
1539
+ detection_mechanism=detection_mechanism,
1540
+ incident_behavior=incident_behavior,
1541
+ tags=tags,
1542
+ created_by=updated_by, # This will be overridden by the actual created_by
1543
+ created_at=now_utc, # This will be overridden by the actual created_at
1544
+ updated_by=updated_by,
1545
+ updated_at=now_utc,
1546
+ schedule=schedule,
1547
+ freshness_schedule_check_type=freshness_schedule_check_type,
1548
+ lookback_window=lookback_window,
1549
+ )
1550
+
1551
+ # 3. Merge the assertion input with the existing assertion and monitor entities or create a new assertion
1552
+ # if the assertion does not exist:
1553
+ merged_assertion_input_or_created_assertion = (
1554
+ self._retrieve_and_merge_freshness_assertion_and_monitor(
1555
+ assertion_input=assertion_input,
1556
+ dataset_urn=dataset_urn,
1557
+ urn=urn,
1558
+ display_name=display_name,
1559
+ enabled=enabled,
1560
+ detection_mechanism=detection_mechanism,
1561
+ incident_behavior=incident_behavior,
1562
+ tags=tags,
1563
+ updated_by=updated_by,
1564
+ now_utc=now_utc,
1565
+ schedule=schedule,
1566
+ freshness_schedule_check_type=freshness_schedule_check_type,
1567
+ lookback_window=lookback_window,
1568
+ )
1569
+ )
1570
+
1571
+ # Return early if we created a new assertion in the merge:
1572
+ if isinstance(merged_assertion_input_or_created_assertion, _AssertionPublic):
1573
+ # We know this is the correct type because we passed the assertion_class parameter
1574
+ assert isinstance(
1575
+ merged_assertion_input_or_created_assertion, FreshnessAssertion
1576
+ )
1577
+ return merged_assertion_input_or_created_assertion
1578
+
1579
+ # 4. Upsert the assertion and monitor entities:
1580
+ assertion_entity, monitor_entity = (
1581
+ merged_assertion_input_or_created_assertion.to_assertion_and_monitor_entities()
1582
+ )
1583
+ # If assertion upsert fails, we won't try to upsert the monitor
1584
+ self.client.entities.upsert(assertion_entity)
1585
+ # 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)
1586
+ # try:
1587
+ self.client.entities.upsert(monitor_entity)
1588
+ # except Exception as e:
1589
+ # logger.error(f"Error upserting monitor: {e}")
1590
+ # self.client.entities.delete(assertion_entity)
1591
+ # raise e
1592
+
1593
+ return FreshnessAssertion._from_entities(assertion_entity, monitor_entity)
1594
+
1109
1595
 
1110
1596
  def _merge_field(
1111
1597
  input_field_value: Any,
@@ -34,7 +34,7 @@ AssertionInfoInputType: TypeAlias = Union[
34
34
  models.FreshnessAssertionInfoClass,
35
35
  models.VolumeAssertionInfoClass,
36
36
  models.SqlAssertionInfoClass,
37
- # TODO: models.FieldAssertionInfoClass,
37
+ models.FieldAssertionInfoClass,
38
38
  # TODO: models.SchemaAssertionInfoClass,
39
39
  # TODO: models.CustomAssertionInfoClass,
40
40
  ]
@@ -189,6 +189,9 @@ class Assertion(HasPlatformInstance, HasTags, Entity):
189
189
  elif isinstance(assertion, models.SqlAssertionInfoClass):
190
190
  info.sqlAssertion = assertion
191
191
  info.type = models.AssertionTypeClass.SQL
192
+ elif isinstance(assertion, models.FieldAssertionInfoClass):
193
+ info.fieldAssertion = assertion
194
+ info.type = models.AssertionTypeClass.FIELD
192
195
  else:
193
196
  assert_never(assertion)
194
197