Skip to content

Commit 302893d

Browse files
committed
Reject source-id next to source-ids, matching main
1 parent 20f8427 commit 302893d

3 files changed

Lines changed: 3 additions & 72 deletions

File tree

‎pyiceberg/transforms.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,14 +1087,15 @@ def map_source_ids_onto_source_id(cls, data: Any) -> Any:
10871087
if not isinstance(data, dict) or "source-ids" not in data:
10881088
return data
10891089

1090+
if "source-id" in data:
1091+
raise ValueError("source-id and source-ids are mutually exclusive")
1092+
10901093
source_ids = data["source-ids"]
10911094
if not isinstance(source_ids, list):
10921095
return data
10931096
if len(source_ids) == 0:
10941097
raise ValueError("Empty source-ids is not allowed")
10951098

1096-
# The spec writes only one of the two keys, so a source-id next to source-ids comes
1097-
# from a non-conformant writer; source-ids is the one that carries the arity
10981099
data["source-id"] = source_ids[0]
10991100
if len(source_ids) == 1:
11001101
data.pop("source-ids", None)

‎tests/table/test_partitioning.py‎

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -389,48 +389,6 @@ def test_deserialize_partition_field_multi_arg_requires_transform() -> None:
389389
PartitionField.model_validate_json(json_partition_spec)
390390

391391

392-
def test_deserialize_partition_field_both_source_id_and_source_ids_multi_arg() -> None:
393-
import json as json_lib
394-
395-
from pyiceberg.transforms import UnknownTransform
396-
397-
# a non-conformant writer emitted both keys; source-ids carries the arity, so it wins
398-
json_partition_spec = (
399-
"""{"source-id": 9, "source-ids": [1, 2], "field-id": 1000, "transform": "bucket[4]", "name": "multi_bucket"}"""
400-
)
401-
field = PartitionField.model_validate_json(json_partition_spec)
402-
403-
assert isinstance(field.transform, UnknownTransform)
404-
assert field.source_id == 1
405-
assert field.source_ids == [1, 2]
406-
407-
serialized = json_lib.loads(field.model_dump_json())
408-
assert serialized["source-ids"] == [1, 2]
409-
assert "source-id" not in serialized
410-
411-
412-
def test_deserialize_partition_field_both_source_id_and_source_ids_single_arg() -> None:
413-
import json as json_lib
414-
415-
json_partition_spec = (
416-
"""{"source-id": 9, "source-ids": [1], "field-id": 1000, "transform": "truncate[19]", "name": "str_truncate"}"""
417-
)
418-
field = PartitionField.model_validate_json(json_partition_spec)
419-
420-
assert field.source_id == 1
421-
assert field.source_ids is None
422-
423-
serialized = json_lib.loads(field.model_dump_json())
424-
assert serialized["source-id"] == 1
425-
assert "source-ids" not in serialized
426-
427-
428-
def test_deserialize_partition_field_empty_source_ids_rejected_next_to_source_id() -> None:
429-
json_partition_spec = """{"source-id": 1, "source-ids": [], "field-id": 1000, "transform": "identity", "name": "x"}"""
430-
with pytest.raises(Exception, match="Empty source-ids is not allowed"):
431-
PartitionField.model_validate_json(json_partition_spec)
432-
433-
434392
def test_partition_field_transform_arguments() -> None:
435393
single = PartitionField(source_id=1, field_id=1000, transform=TruncateTransform(width=19), name="str_truncate")
436394
assert single.transform_arguments == [1]

‎tests/table/test_sorting.py‎

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -208,34 +208,6 @@ def test_deserialize_sort_field_multi_arg_requires_transform() -> None:
208208
SortField.model_validate_json(payload)
209209

210210

211-
def test_deserialize_sort_field_both_source_id_and_source_ids_multi_arg() -> None:
212-
from pyiceberg.transforms import UnknownTransform
213-
214-
# a non-conformant writer emitted both keys; source-ids carries the arity, so it wins
215-
payload = '{"source-id":9,"source-ids":[19,20],"transform":"bucket[4]","direction":"asc","null-order":"nulls-first"}'
216-
field = SortField.model_validate_json(payload)
217-
218-
assert isinstance(field.transform, UnknownTransform)
219-
assert field.source_id == 19
220-
assert field.source_ids == [19, 20]
221-
222-
serialized = json.loads(field.model_dump_json())
223-
assert serialized["source-ids"] == [19, 20]
224-
assert "source-id" not in serialized
225-
226-
227-
def test_deserialize_sort_field_both_source_id_and_source_ids_single_arg() -> None:
228-
payload = '{"source-id":9,"source-ids":[19],"transform":"identity","direction":"asc","null-order":"nulls-first"}'
229-
field = SortField.model_validate_json(payload)
230-
231-
assert field.source_id == 19
232-
assert field.source_ids is None
233-
234-
serialized = json.loads(field.model_dump_json())
235-
assert serialized["source-id"] == 19
236-
assert "source-ids" not in serialized
237-
238-
239211
def test_sort_field_transform_arguments() -> None:
240212
single = SortField(source_id=19, transform=BucketTransform(num_buckets=4), null_order=NullOrder.NULLS_FIRST)
241213
assert single.transform_arguments == [19]

0 commit comments

Comments
 (0)