4444from sortedcontainers import SortedList
4545from typing_extensions import Annotated
4646
47- from pyiceberg .exceptions import ResolveError , ValidationError
47+ from pyiceberg .exceptions import CommitFailedException , ResolveError , ValidationError
4848from pyiceberg .expressions import (
4949 AlwaysTrue ,
5050 And ,
@@ -540,18 +540,40 @@ def update_table_metadata(base_metadata: TableMetadata, updates: Tuple[TableUpda
540540class TableRequirement (IcebergBaseModel ):
541541 type : str
542542
543+ @abstractmethod
544+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
545+ """Validate the requirement against the base metadata.
546+
547+ Args:
548+ base_metadata: The base metadata to be validated against.
549+
550+ Raises:
551+ CommitFailedException: When the requirement is not met.
552+ """
553+ ...
554+
543555
544556class AssertCreate (TableRequirement ):
545557 """The table must not already exist; used for create transactions."""
546558
547559 type : Literal ["assert-create" ] = Field (default = "assert-create" )
548560
561+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
562+ if base_metadata is not None :
563+ raise CommitFailedException ("Table already exists" )
564+
549565
550566class AssertTableUUID (TableRequirement ):
551567 """The table UUID must match the requirement's `uuid`."""
552568
553569 type : Literal ["assert-table-uuid" ] = Field (default = "assert-table-uuid" )
554- uuid : str
570+ uuid : uuid .UUID
571+
572+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
573+ if base_metadata is None :
574+ raise CommitFailedException ("Requirement failed: current table metadata is missing" )
575+ elif self .uuid != base_metadata .table_uuid :
576+ raise CommitFailedException (f"Table UUID does not match: { self .uuid } != { base_metadata .table_uuid } " )
555577
556578
557579class AssertRefSnapshotId (TableRequirement ):
@@ -564,41 +586,95 @@ class AssertRefSnapshotId(TableRequirement):
564586 ref : str
565587 snapshot_id : Optional [int ] = Field (default = None , alias = "snapshot-id" )
566588
589+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
590+ if base_metadata is None :
591+ raise CommitFailedException ("Requirement failed: current table metadata is missing" )
592+ elif snapshot_ref := base_metadata .refs .get (self .ref ):
593+ ref_type = snapshot_ref .snapshot_ref_type
594+ if self .snapshot_id is None :
595+ raise CommitFailedException (f"Requirement failed: { ref_type } { self .ref } was created concurrently" )
596+ elif self .snapshot_id != snapshot_ref .snapshot_id :
597+ raise CommitFailedException (
598+ f"Requirement failed: { ref_type } { self .ref } has changed: expected id { self .snapshot_id } , found { snapshot_ref .snapshot_id } "
599+ )
600+ elif self .snapshot_id is not None :
601+ raise CommitFailedException (f"Requirement failed: branch or tag { self .ref } is missing, expected { self .snapshot_id } " )
602+
567603
568604class AssertLastAssignedFieldId (TableRequirement ):
569605 """The table's last assigned column id must match the requirement's `last-assigned-field-id`."""
570606
571607 type : Literal ["assert-last-assigned-field-id" ] = Field (default = "assert-last-assigned-field-id" )
572608 last_assigned_field_id : int = Field (..., alias = "last-assigned-field-id" )
573609
610+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
611+ if base_metadata is None :
612+ raise CommitFailedException ("Requirement failed: current table metadata is missing" )
613+ elif base_metadata .last_column_id != self .last_assigned_field_id :
614+ raise CommitFailedException (
615+ f"Requirement failed: last assigned field id has changed: expected { self .last_assigned_field_id } , found { base_metadata .last_column_id } "
616+ )
617+
574618
575619class AssertCurrentSchemaId (TableRequirement ):
576620 """The table's current schema id must match the requirement's `current-schema-id`."""
577621
578622 type : Literal ["assert-current-schema-id" ] = Field (default = "assert-current-schema-id" )
579623 current_schema_id : int = Field (..., alias = "current-schema-id" )
580624
625+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
626+ if base_metadata is None :
627+ raise CommitFailedException ("Requirement failed: current table metadata is missing" )
628+ elif self .current_schema_id != base_metadata .current_schema_id :
629+ raise CommitFailedException (
630+ f"Requirement failed: current schema id has changed: expected { self .current_schema_id } , found { base_metadata .current_schema_id } "
631+ )
632+
581633
582634class AssertLastAssignedPartitionId (TableRequirement ):
583635 """The table's last assigned partition id must match the requirement's `last-assigned-partition-id`."""
584636
585637 type : Literal ["assert-last-assigned-partition-id" ] = Field (default = "assert-last-assigned-partition-id" )
586638 last_assigned_partition_id : int = Field (..., alias = "last-assigned-partition-id" )
587639
640+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
641+ if base_metadata is None :
642+ raise CommitFailedException ("Requirement failed: current table metadata is missing" )
643+ elif base_metadata .last_partition_id != self .last_assigned_partition_id :
644+ raise CommitFailedException (
645+ f"Requirement failed: last assigned partition id has changed: expected { self .last_assigned_partition_id } , found { base_metadata .last_partition_id } "
646+ )
647+
588648
589649class AssertDefaultSpecId (TableRequirement ):
590650 """The table's default spec id must match the requirement's `default-spec-id`."""
591651
592652 type : Literal ["assert-default-spec-id" ] = Field (default = "assert-default-spec-id" )
593653 default_spec_id : int = Field (..., alias = "default-spec-id" )
594654
655+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
656+ if base_metadata is None :
657+ raise CommitFailedException ("Requirement failed: current table metadata is missing" )
658+ elif self .default_spec_id != base_metadata .default_spec_id :
659+ raise CommitFailedException (
660+ f"Requirement failed: default spec id has changed: expected { self .default_spec_id } , found { base_metadata .default_spec_id } "
661+ )
662+
595663
596664class AssertDefaultSortOrderId (TableRequirement ):
597665 """The table's default sort order id must match the requirement's `default-sort-order-id`."""
598666
599667 type : Literal ["assert-default-sort-order-id" ] = Field (default = "assert-default-sort-order-id" )
600668 default_sort_order_id : int = Field (..., alias = "default-sort-order-id" )
601669
670+ def validate (self , base_metadata : Optional [TableMetadata ]) -> None :
671+ if base_metadata is None :
672+ raise CommitFailedException ("Requirement failed: current table metadata is missing" )
673+ elif self .default_sort_order_id != base_metadata .default_sort_order_id :
674+ raise CommitFailedException (
675+ f"Requirement failed: default sort order id has changed: expected { self .default_sort_order_id } , found { base_metadata .default_sort_order_id } "
676+ )
677+
602678
603679class Namespace (IcebergRootModel [List [str ]]):
604680 """Reference to one or more levels of a namespace."""
0 commit comments