fix(python): support custom blob thresholds without pylance (#4634)

This commit is contained in:
lancedb-gatefixer[bot]
2026-10-23 14:37:07 -07:00
committed by GitHub
parent 9da4658d57
commit 27b65064d0
3 changed files with 193 additions and 24 deletions
+118
View File
@@ -59,6 +59,124 @@ def _blob_v2_table(db: DBConnection, name: str):
return table
@pytest.mark.parametrize(
"threshold",
[
"inline_size_threshold",
"dedicated_size_threshold",
"pack_file_size_threshold",
],
)
@pytest.mark.parametrize(
"value,error",
[
(-1, ValueError),
(True, TypeError),
(False, TypeError),
(1.5, TypeError),
("8192", TypeError),
(ctypes.c_size_t(-1).value + 1, OverflowError),
],
)
def test_blob_thresholds_reject_invalid_values(threshold, value, error):
# Also runs in CI with pylance actually uninstalled.
with pytest.raises(error, match=f"{threshold} must"):
lancedb.blob("blob", **{threshold: value})
@pytest.mark.parametrize(
"threshold", ["dedicated_size_threshold", "pack_file_size_threshold"]
)
def test_blob_thresholds_reject_zero_for_positive_thresholds(threshold):
with pytest.raises(ValueError, match=f"{threshold} must be positive"):
lancedb.blob("blob", **{threshold: 0})
def test_blob_thresholds_accept_integer_boundaries():
maximum = ctypes.c_size_t(-1).value
field = lancedb.blob(
"blob",
nullable=False,
inline_size_threshold=0,
dedicated_size_threshold=maximum,
pack_file_size_threshold=maximum,
)
assert not field.nullable
assert field.metadata == {
b"lance-encoding:blob-inline-size-threshold": b"0",
b"lance-encoding:blob-dedicated-size-threshold": str(maximum).encode(),
b"lance-encoding:blob-pack-file-size-threshold": str(maximum).encode(),
}
def test_blob_thresholds_control_writes_after_reopen(tmp_path):
# Keep this in test_table.py: CI runs it without pylance or pandas installed.
field = lancedb.blob(
"blob",
inline_size_threshold=4,
dedicated_size_threshold=12,
pack_file_size_threshold=8,
)
metadata = {
b"lance-encoding:blob-inline-size-threshold": b"4",
b"lance-encoding:blob-dedicated-size-threshold": b"12",
b"lance-encoding:blob-pack-file-size-threshold": b"8",
}
assert field.metadata == metadata
schema = pa.schema([pa.field("id", pa.int64()), field])
db = lancedb.connect(tmp_path)
table = db.create_table("blobs", schema=schema)
# Every payload fits in the default inline tier; custom thresholds must
# instead send the larger values to packed and dedicated storage.
rows = [
{"id": 0, "blob": b"a" * 2},
{"id": 1, "blob": b"b" * 8},
{"id": 2, "blob": b"c" * 16},
{"id": 3, "blob": b"d" * 8},
]
def check_writes(table, expected):
assert table.schema.field("blob").metadata.items() >= metadata.items()
hits = table.search().with_row_id(True).limit(10).to_arrow()
actual = {row["id"]: row for row in hits.to_pylist()}
assert actual.keys() == {row["id"] for row in expected}
for row in expected:
size = len(row["blob"])
descriptor = actual[row["id"]]["blob"]
assert descriptor["size"] == size
assert descriptor["kind"] == (2 if size > 12 else 1 if size > 4 else 0)
row_ids = [actual[row["id"]]["_rowid"] for row in expected]
assert table.fetch_blobs("blob", row_ids).to_pylist() == [
row["blob"] for row in expected
]
# Two packed values reach the configured sidecar limit independently.
packed = [
actual[row["id"]]["blob"] for row in expected if len(row["blob"]) == 8
]
assert len({desc["blob_id"] for desc in packed}) == len(packed)
table.add(rows)
check_writes(table, rows)
table = lancedb.connect(tmp_path).open_table("blobs")
check_writes(table, rows)
updates = [
{"id": 0, "blob": b"e" * 16},
{"id": 1, "blob": b"f" * 2},
{"id": 2, "blob": b"g" * 8},
{"id": 3, "blob": b"h" * 8},
{"id": 4, "blob": b"i" * 16},
]
result = (
table.merge_insert("id")
.when_matched_update_all()
.when_not_matched_insert_all()
.execute(updates)
)
assert result.num_updated_rows == 4
assert result.num_inserted_rows == 1
check_writes(lancedb.connect(tmp_path).open_table("blobs"), updates)
async def _blob_v2_table_async(db: AsyncConnection, name: str):
schema = pa.schema([pa.field("id", pa.int64()), lancedb.blob("blob")])
table = await db.create_table(name, schema=schema)