Skip to content

Commit 7df02e6

Browse files
committed
test(logical_types): add set[T] native element round-trip tests for ITL-611
Covers set[int|str|float|bool|bytes|datetime] through Parquet and Delta backends, schema reconstruction, list[int] regression guard, explicit ListLogicalType(int, is_set=False) construction, and fresh-converter read-back without prior registration.
1 parent 1205a09 commit 7df02e6

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

tests/test_logical_types/test_roundtrips.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,3 +763,142 @@ def test_explicit_native_list_construction() -> None:
763763
assert storage == [1, 2, 3]
764764
result = lt.storage_to_python([1, 2, 3], converter=None)
765765
assert result == [1, 2, 3]
766+
767+
768+
# ── set[T] native element full round-trip tests (ITL-611) ─────────────────────
769+
770+
771+
def test_set_of_int_round_trip(storage_backend: _StorageBackend, tmp_path: Path) -> None:
772+
"""set[int] values round-trip as sets, not lists; extension name is 'set[int]'."""
773+
data = {1, 2, 3}
774+
result, read_converter = _write_and_read(
775+
{"s": set[int]},
776+
[{"s": data}],
777+
storage_backend,
778+
tmp_path,
779+
)
780+
field = result.schema.field("s")
781+
assert hasattr(field.type, "extension_name"), (
782+
f"Expected extension type on 's', got {field.type!r}"
783+
)
784+
assert field.type.extension_name == "set[int]"
785+
rows = read_converter.arrow_table_to_python_dicts(result)
786+
assert len(rows) == 1
787+
assert isinstance(rows[0]["s"], set), f"Expected set, got {type(rows[0]['s'])}"
788+
assert rows[0]["s"] == data
789+
790+
791+
def test_set_of_str_round_trip(storage_backend: _StorageBackend, tmp_path: Path) -> None:
792+
"""set[str] values round-trip as sets; extension name is 'set[str]'."""
793+
data = {"alpha", "beta", "gamma"}
794+
result, read_converter = _write_and_read(
795+
{"tags": set[str]},
796+
[{"tags": data}],
797+
storage_backend,
798+
tmp_path,
799+
)
800+
field = result.schema.field("tags")
801+
assert hasattr(field.type, "extension_name")
802+
assert field.type.extension_name == "set[str]"
803+
rows = read_converter.arrow_table_to_python_dicts(result)
804+
assert isinstance(rows[0]["tags"], set)
805+
assert rows[0]["tags"] == data
806+
807+
808+
def test_set_of_float_round_trip(storage_backend: _StorageBackend, tmp_path: Path) -> None:
809+
"""set[float] values round-trip as sets; extension name is 'set[float]'."""
810+
data = {1.0, 2.5, 3.14}
811+
result, read_converter = _write_and_read(
812+
{"values": set[float]},
813+
[{"values": data}],
814+
storage_backend,
815+
tmp_path,
816+
)
817+
field = result.schema.field("values")
818+
assert hasattr(field.type, "extension_name")
819+
assert field.type.extension_name == "set[float]"
820+
rows = read_converter.arrow_table_to_python_dicts(result)
821+
assert isinstance(rows[0]["values"], set)
822+
assert rows[0]["values"] == data
823+
824+
825+
def test_set_of_bool_round_trip(storage_backend: _StorageBackend, tmp_path: Path) -> None:
826+
"""set[bool] values round-trip as sets; extension name is 'set[bool]'."""
827+
data = {True, False}
828+
result, read_converter = _write_and_read(
829+
{"flags": set[bool]},
830+
[{"flags": data}],
831+
storage_backend,
832+
tmp_path,
833+
)
834+
field = result.schema.field("flags")
835+
assert hasattr(field.type, "extension_name")
836+
assert field.type.extension_name == "set[bool]"
837+
rows = read_converter.arrow_table_to_python_dicts(result)
838+
assert isinstance(rows[0]["flags"], set)
839+
assert rows[0]["flags"] == data
840+
841+
842+
def test_set_of_bytes_round_trip(storage_backend: _StorageBackend, tmp_path: Path) -> None:
843+
"""set[bytes] values round-trip as sets; extension name is 'set[bytes]'."""
844+
data = {b"foo", b"bar", b"baz"}
845+
result, read_converter = _write_and_read(
846+
{"blobs": set[bytes]},
847+
[{"blobs": data}],
848+
storage_backend,
849+
tmp_path,
850+
)
851+
field = result.schema.field("blobs")
852+
assert hasattr(field.type, "extension_name")
853+
assert field.type.extension_name == "set[bytes]"
854+
rows = read_converter.arrow_table_to_python_dicts(result)
855+
assert isinstance(rows[0]["blobs"], set)
856+
assert rows[0]["blobs"] == data
857+
858+
859+
def test_set_of_datetime_round_trip(storage_backend: _StorageBackend, tmp_path: Path) -> None:
860+
"""set[datetime] values round-trip as sets of timezone-aware datetimes."""
861+
from datetime import datetime, timezone
862+
dt1 = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
863+
dt2 = datetime(2024, 6, 15, 12, 30, 0, tzinfo=timezone.utc)
864+
data = {dt1, dt2}
865+
result, read_converter = _write_and_read(
866+
{"timestamps": set[datetime]},
867+
[{"timestamps": data}],
868+
storage_backend,
869+
tmp_path,
870+
)
871+
field = result.schema.field("timestamps")
872+
assert hasattr(field.type, "extension_name")
873+
assert field.type.extension_name == "set[datetime]"
874+
rows = read_converter.arrow_table_to_python_dicts(result)
875+
assert isinstance(rows[0]["timestamps"], set)
876+
assert rows[0]["timestamps"] == data
877+
878+
879+
def test_fresh_converter_reads_set_of_int(
880+
storage_backend: _StorageBackend, tmp_path: Path
881+
) -> None:
882+
"""A fresh converter (no prior registration) reconstructs set[int] via load_logical_types."""
883+
data = {1, 2, 3}
884+
885+
# Write with converter A.
886+
write_converter = _fresh_converter()
887+
write_converter.register_python_class(set[int])
888+
arrow_schema = write_converter.python_schema_to_arrow_schema({"s": set[int]})
889+
table = write_converter.python_dicts_to_arrow_table([{"s": data}], arrow_schema=arrow_schema)
890+
storage_backend.write(table, tmp_path)
891+
892+
# Read with converter B — no prior registration; load_logical_types triggers factory.
893+
read_converter = _fresh_converter()
894+
result = storage_backend.read(tmp_path, read_converter)
895+
896+
field = result.schema.field("s")
897+
assert hasattr(field.type, "extension_name"), (
898+
f"Expected extension type after fresh-converter read, got {field.type!r}"
899+
)
900+
assert field.type.extension_name == "set[int]"
901+
902+
rows = read_converter.arrow_table_to_python_dicts(result)
903+
assert isinstance(rows[0]["s"], set)
904+
assert rows[0]["s"] == data

0 commit comments

Comments
 (0)