Skip to content

Commit f98a8d1

Browse files
committed
Fix platform fallback metadata and compatibility boundaries
1 parent 61714ca commit f98a8d1

6 files changed

Lines changed: 37 additions & 6 deletions

File tree

‎src/blosc2/ctable_nulls.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,7 +958,9 @@ def null_pred(self):
958958
return None
959959
if _is_nan(sentinel):
960960
return blosc2.isnan(col._raw_col)
961-
return col._raw_col == sentinel
961+
# This internal predicate must remain lazy even while expression fusion
962+
# temporarily disables user-facing overloaded equality for identity checks.
963+
return blosc2.LazyExpr(new_op=(col._raw_col, "==", sentinel))
962964

963965
def valid_pred(self):
964966
"""Lazy predicate over the raw physical array, True where the value is *not* null.

‎src/blosc2/expression_graph.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,8 +1008,11 @@ def _root_reduction_dtype(name, receiver, arguments, backend):
10081008
):
10091009
# Single-threaded WASM uses the existing NumPy reduction fallback,
10101010
# whose default integer accumulator/index width is platform-sized.
1011-
# Do not replace that numerical contract with native Blosc2's int64.
1012-
return None
1011+
# Dummy-array inference can still return native int64 for positional
1012+
# argmin/argmax. State the NumPy fallback's width explicitly instead.
1013+
return np.dtype(
1014+
np.uintp if input_dtype.kind == "u" and name not in {"argmin", "argmax"} else np.intp
1015+
)
10131016
if name in _CUMULATIVE_OPERATIONS and backend != "numpy" and requested is not None:
10141017
# The current Blosc2 cumulative implementation does not consistently
10151018
# apply dtype overrides. Preserve its inference rather than claiming

‎src/blosc2/zarr_source.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ def counting_store(zarr, store, traffic, *, metadata=None):
4343
class CountingStore(zarr.storage.WrapperStore):
4444
_blosc2_traffic = traffic
4545

46+
def with_read_only(self, read_only=False):
47+
# Zarr 3.1's WrapperStore inherits an unimplemented base method.
48+
# Retain traffic/metadata accounting when Zarr opens a writable
49+
# authorized store for reading.
50+
return CountingStore(self._store.with_read_only(read_only))
51+
4652
async def get(self, key, prototype, byte_range=None):
4753
is_metadata = (
4854
metadata is not None

‎tests/ctable/test_null_channel.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,14 +269,16 @@ class Row:
269269
np.testing.assert_array_equal(col.is_null(), [False, True])
270270

271271

272-
def test_null_pred_matches_is_null_for_sentinel_column():
272+
@pytest.mark.parametrize("identity_equal", [False, True])
273+
def test_null_pred_matches_is_null_for_sentinel_column(monkeypatch, identity_equal):
273274
@dataclass
274275
class Row:
275276
v: int = blosc2.field(blosc2.int64(null_value=-1))
276277

277278
t = CTable(Row)
278279
t.extend({"v": [1, -1, 3]})
279280
col = t["v"]
281+
monkeypatch.setattr(blosc2, "_disable_overloaded_equal", identity_equal)
280282
pred = np.asarray(col._nulls.null_pred().compute()[:])
281283
np.testing.assert_array_equal(pred[: t.nrows], col.is_null())
282284
valid = np.asarray(col._nulls.valid_pred().compute()[:])

‎tests/ctable/test_remote_ctable.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ def test_sparse_table_cache_budget(tmp_path, limit):
672672

673673
@pytest.mark.parametrize("include_note", [False, True])
674674
@pytest.mark.parametrize("nrows", [0, 2, 20, 21])
675+
@needs_utf8
675676
def test_remote_example_total_timing(tmp_path, capsys, include_note, nrows):
676677
import runpy
677678
from pathlib import Path
@@ -701,6 +702,7 @@ def test_remote_example_total_timing(tmp_path, capsys, include_note, nrows):
701702
assert sample == str(local[start:stop]).strip()
702703

703704

705+
@needs_utf8
704706
def test_remote_example_cache_dir(tmp_path, capsys, monkeypatch):
705707
import runpy
706708
import sys
@@ -720,6 +722,7 @@ def test_remote_example_cache_dir(tmp_path, capsys, monkeypatch):
720722
assert "(0 requests," in output.split("Total network :")[1]
721723

722724

725+
@needs_utf8
723726
def test_remote_example_batch_columns(tmp_path, capsys):
724727
import runpy
725728
from pathlib import Path
@@ -748,6 +751,7 @@ def test_remote_example_batch_columns(tmp_path, capsys):
748751

749752
@pytest.mark.parametrize(("format_flag", "suffix"), [("pytables", ".h5"), ("parquet", ".parquet")])
750753
@pytest.mark.parametrize("explicit", [False, True])
754+
@needs_utf8
751755
def test_remote_example_external_formats(tmp_path, capsys, monkeypatch, format_flag, suffix, explicit):
752756
import runpy
753757
import sys
@@ -804,6 +808,7 @@ def test_remote_example_external_formats(tmp_path, capsys, monkeypatch, format_f
804808

805809

806810
@pytest.mark.parametrize("explicit", [False, True])
811+
@needs_utf8
807812
def test_remote_example_blosc2_flag_and_inference(tmp_path, capsys, monkeypatch, explicit):
808813
import runpy
809814
import sys
@@ -822,6 +827,7 @@ def test_remote_example_blosc2_flag_and_inference(tmp_path, capsys, monkeypatch,
822827
assert "[Format: Blosc2 B2Z" in capsys.readouterr().out
823828

824829

830+
@needs_utf8
825831
def test_remote_example_unknown_extension(tmp_path, monkeypatch, capsys):
826832
import runpy
827833
import sys
@@ -836,6 +842,7 @@ def test_remote_example_unknown_extension(tmp_path, monkeypatch, capsys):
836842
assert "cannot infer format" in capsys.readouterr().err
837843

838844

845+
@needs_utf8
839846
def test_remote_example_url_extension_ignores_query_string(monkeypatch):
840847
import runpy
841848
import sys
@@ -882,6 +889,7 @@ def changed_info(self, path, **kwargs):
882889
@pytest.mark.parametrize("max_concurrency", [1, 8])
883890
@pytest.mark.parametrize("legacy", [False, True])
884891
@pytest.mark.usefixtures("b2z_range_reads")
892+
@needs_utf8
885893
def test_disk_cache_reuses_ctable_bootstrap(tmp_path, monkeypatch, max_concurrency, legacy):
886894
schema = dataclasses.make_dataclass("Sample", [("x", float), ("note", str, blosc2.field(blosc2.utf8()))])
887895
values = np.random.default_rng(42).random(20_000)
@@ -982,6 +990,7 @@ def unexpected_read(*args, **kwargs):
982990
@pytest.mark.parametrize(
983991
"policy", [blosc2.CachePolicy.NONE, blosc2.CachePolicy.MEMORY, blosc2.CachePolicy.DISK]
984992
)
993+
@needs_utf8
985994
def test_remote_ctable_refresh(tmp_path, policy):
986995
schema = dataclasses.make_dataclass("Sample", [("x", int), ("note", str, blosc2.field(blosc2.utf8()))])
987996
local = blosc2.CTable(schema, [(1, "old"), (2, "café")], create_summary_index=False)
@@ -1504,6 +1513,7 @@ def test_remote_store_returns_table_with_independent_lifetime(tmp_path):
15041513

15051514

15061515
@pytest.mark.usefixtures("b2z_range_reads")
1516+
@needs_utf8
15071517
def test_remote_ctable_reference_save_roundtrip(tmp_path):
15081518
@dataclasses.dataclass
15091519
class TextRow:
@@ -1678,6 +1688,7 @@ class Mixed:
16781688
@pytest.mark.parametrize("policy", list(blosc2.CachePolicy))
16791689
@pytest.mark.parametrize("null_storage", ["mask", "sentinel"])
16801690
@pytest.mark.parametrize("deleted", [False, True])
1691+
@needs_utf8
16811692
def test_remote_ctable_utf8(tmp_path, policy, null_storage, deleted):
16821693
@dataclasses.dataclass
16831694
class TextRow:
@@ -1806,6 +1817,7 @@ def test_remote_ctable_deleted_rows_and_disk_cache(tmp_path):
18061817

18071818

18081819
@pytest.mark.parametrize("empty", [False, True])
1820+
@needs_utf8
18091821
def test_remote_utf8_nested_lifetime(tmp_path, empty):
18101822
@dataclasses.dataclass
18111823
class TextRow:
@@ -1843,6 +1855,7 @@ class TextRow:
18431855

18441856

18451857
@pytest.mark.parametrize("bad_data", [None, np.arange(4, dtype="int64")])
1858+
@needs_utf8
18461859
def test_remote_utf8_invalid_companion(tmp_path, bad_data):
18471860
@dataclasses.dataclass
18481861
class TextRow:
@@ -1878,6 +1891,7 @@ class TextRow:
18781891
@pytest.mark.parametrize("policy", list(blosc2.CachePolicy))
18791892
@pytest.mark.parametrize("blocks", [False, True])
18801893
@pytest.mark.usefixtures("b2z_range_reads")
1894+
@needs_utf8
18811895
def test_remote_utf8_bounded_transfer(tmp_path, monkeypatch, policy, blocks):
18821896
from blosc2 import proxy_source
18831897
from blosc2._utf8_array import UTF8Array
@@ -1970,6 +1984,7 @@ def test_remote_store_rejects_table_root(tmp_path):
19701984

19711985

19721986
@pytest.mark.parametrize("policy", list(blosc2.CachePolicy))
1987+
@needs_utf8
19731988
def test_open_dispatches_local_and_remote_tables(tmp_path, policy):
19741989
@dataclasses.dataclass
19751990
class TextRow:
@@ -2062,6 +2077,7 @@ def counted(self, path, start=None, end=None, **kwargs):
20622077

20632078

20642079
@pytest.mark.usefixtures("b2z_range_reads")
2080+
@needs_utf8
20652081
def test_parallel_metadata_benchmark(tmp_path, monkeypatch):
20662082
import runpy
20672083
import time
@@ -2144,6 +2160,7 @@ def fail(*args):
21442160
"policy", [blosc2.CachePolicy.NONE, blosc2.CachePolicy.MEMORY, blosc2.CachePolicy.DISK]
21452161
)
21462162
@pytest.mark.parametrize("budget", [128, 1 << 20])
2163+
@needs_utf8
21472164
def test_parallel_rows_cache_policies(tmp_path, policy, budget):
21482165
@dataclasses.dataclass
21492166
class Mixed:
@@ -2304,6 +2321,7 @@ def write(self, *args):
23042321
assert list(table[399990:]) == list(local[399990:])
23052322

23062323

2324+
@needs_utf8
23072325
def test_parallel_timestamp_and_projection(tmp_path):
23082326
@dataclasses.dataclass
23092327
class Timed:

‎tests/test_expression_graph_properties.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ def test_value_dependent_numpy_promotion_defers_scalar_metadata(monkeypatch):
4848

4949

5050
@pytest.mark.parametrize("name", ["sum", "prod", "argmin", "argmax", "cumulative_sum", "cumulative_prod"])
51-
def test_platform_accumulator_fallback_does_not_claim_native_integer_width(monkeypatch, name):
51+
def test_platform_accumulator_fallback_uses_numpy_integer_width(monkeypatch, name):
5252
module = importlib.import_module("blosc2.expression_graph")
5353
monkeypatch.setattr(module, "_BLOSC_PLATFORM_ACCUMULATOR_FALLBACK", True)
5454
source = blosc2.asarray(np.arange(1, 5, dtype="int8"))
5555
graph = module.parse_expression(f"x.{name}(axis=0)")
56-
assert graph.infer_dtype({"x": source}) is None
56+
assert graph.infer_dtype({"x": source}) == np.dtype(np.intp)
5757
numpy_graph = module.parse_expression("np.sum(x)")
5858
assert numpy_graph.infer_dtype({"x": np.arange(4, dtype="int8")}) == np.dtype(np.intp)
5959

0 commit comments

Comments
 (0)