chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
@@ -0,0 +1,937 @@
import logging
import os
from pathlib import Path
from typing import Any
import fsspec
import numpy as np
import pandas as pd
import pyarrow.fs
import pytest
import zarr
from pytest_lazy_fixtures import lf as lazy_fixture
import ray
from ray.data._internal.datasource import zarrv2_datasource
from ray.data.block import BlockAccessor
from ray.data.tests.conftest import * # noqa: F401, F403
def _execute_read_tasks(tasks) -> pd.DataFrame:
frames = [
BlockAccessor.for_block(block).to_pandas() for task in tasks for block in task()
]
return pd.concat(frames, ignore_index=True)
def _reconstruct_array(df: pd.DataFrame, array_name: str) -> np.ndarray:
"""Concatenate all chunks of one array from a long-form result frame."""
sub = df[df["array"] == array_name].sort_values(
"chunk_index", key=lambda col: col.map(tuple)
)
return np.concatenate(list(sub["chunk"]), axis=0)
def _write_real_zarr_store(
store_path: Path,
arrays: dict, # {name: (data, chunks)}
) -> Path:
"""Write a real Zarr v2 store from numpy arrays and consolidate metadata."""
root = zarr.open_group(str(store_path), mode="w")
for name, (data, chunks) in arrays.items():
root.create_dataset(name, data=data, chunks=chunks, dtype=data.dtype)
zarr.consolidate_metadata(zarr.DirectoryStore(str(store_path)))
return store_path
@pytest.fixture
def zarrv2_group_store(tmp_path) -> Path:
"""Two arrays at the store root, both 2-D and 1-D, axis-0-aligned (shape[0]==5)."""
return _write_real_zarr_store(
tmp_path / "group.zarr",
{
"images": (np.arange(20, dtype="<i4").reshape(5, 4), (2, 4)),
"nested": (np.arange(5, dtype="|u1"), (2,)),
},
)
@pytest.fixture
def zarrv2_root_store(tmp_path) -> Path:
"""Single-array store with the array sitting directly at the store root."""
store_path = tmp_path / "root.zarr"
arr = zarr.open(
str(store_path),
mode="w",
shape=(5, 4),
chunks=(2, 4),
dtype="<i4",
)
arr[:] = np.arange(20, dtype="<i4").reshape(5, 4)
zarr.consolidate_metadata(zarr.DirectoryStore(str(store_path)))
return store_path
@pytest.fixture
def local_fsspec_fs():
"""fsspec local filesystem (for parametrized cross-fs read tests)."""
return fsspec.filesystem("file")
@pytest.fixture
def heterogeneous_zarrv2_store(tmp_path) -> Path:
"""A store mixing different ranks, shape[0]s, dtypes, and native chunk sizes.
Mirrors the UMI-style real-world layout where ``data/*`` arrays share an
axis-0 timestep count but differ in everything else, and ``meta/*``
arrays live in a separate axis-0 universe entirely. The chunk-per-row
datasource handles all of these in one read; nothing has to align.
"""
store_path = tmp_path / "heterogeneous.zarr"
root = zarr.open_group(str(store_path), mode="w")
# 4-D image tensor with tiny axis-0 chunks (1 image per chunk).
root.create_dataset(
"data/camera0_rgb",
data=np.arange(20 * 2 * 2 * 3, dtype="|u1").reshape(20, 2, 2, 3),
chunks=(1, 2, 2, 3),
)
# 2-D pose array, same shape[0]=20, much larger axis-0 chunks (10).
root.create_dataset(
"data/robot0_eef_pos",
data=np.arange(20 * 3, dtype="<f4").reshape(20, 3),
chunks=(10, 3),
)
# Episode-boundary metadata: separate axis-0 universe.
root.create_dataset(
"meta/episode_ends",
data=np.array([5, 12, 20], dtype="<i8"),
chunks=(3,),
)
zarr.consolidate_metadata(zarr.DirectoryStore(str(store_path)))
return store_path
@pytest.fixture
def unconsolidated_zarrv2_store(tmp_path) -> Path:
"""Two arrays at the store root, no ``.zmetadata``.
Exercises the no-``.zmetadata`` code paths (per-array ``.zarray``
discovery and full-store walk) — the common shape of real-world stores
behind plain HTTPS or other listing-less filesystems.
"""
store_path = tmp_path / "unconsolidated.zarr"
root = zarr.open_group(str(store_path), mode="w")
root.create_dataset(
"images", data=np.arange(20, dtype="<i4").reshape(5, 4), chunks=(2, 4)
)
root.create_dataset("nested", data=np.arange(5, dtype="|u1"), chunks=(2,))
return store_path
@pytest.fixture
def aligned_zarrv2_store(tmp_path) -> Path:
"""Three arrays sharing ``shape[0]=8``, different ranks and native chunks.
Models the UMI-style case where data arrays co-stride on the timestep
axis but differ in everything else.
"""
store_path = tmp_path / "aligned.zarr"
root = zarr.open_group(str(store_path), mode="w")
root.create_dataset(
"img",
data=np.arange(8 * 4 * 4 * 3, dtype="|u1").reshape(8, 4, 4, 3),
chunks=(2, 4, 4, 3),
)
root.create_dataset(
"state",
data=np.arange(8 * 3, dtype="<f4").reshape(8, 3),
chunks=(4, 3), # different native axis-0 chunks than img
)
root.create_dataset(
"label",
data=np.arange(8, dtype="<i8"),
chunks=(8,),
)
zarr.consolidate_metadata(zarr.DirectoryStore(str(store_path)))
return store_path
@pytest.fixture
def zarr_zip_store(tmp_path) -> Path:
"""A small Zarr store packed into a ``.zip`` for URL-detection tests."""
src = tmp_path / "src.zarr"
_write_real_zarr_store(
src,
{
"data": (np.arange(12, dtype="<i4").reshape(6, 2), (3, 2)),
},
)
zip_path = tmp_path / "store.zarr.zip"
import shutil
shutil.make_archive(
base_name=str(tmp_path / "store.zarr"),
format="zip",
root_dir=str(src),
)
assert zip_path.exists()
return zip_path
# ---------------------------------------------------------------------------
# Metadata discovery
# ---------------------------------------------------------------------------
def test_normalizes_requested_root_array_path(zarrv2_root_store):
datasource = zarrv2_datasource.ZarrV2Datasource(
str(zarrv2_root_store),
array_paths=[""],
)
assert list(datasource._metadata_by_path) == [""]
def test_normalizes_requested_array_paths(zarrv2_group_store):
datasource = zarrv2_datasource.ZarrV2Datasource(
str(zarrv2_group_store),
array_paths=["images/", "nested"],
)
assert list(datasource._metadata_by_path) == ["images", "nested"]
def test_rejects_missing_array_paths(zarrv2_group_store):
with pytest.raises(
ValueError,
match=r"Array\(s\) not found: 'missing'\. Available: 'images', 'nested'",
):
zarrv2_datasource.ZarrV2Datasource(
str(zarrv2_group_store),
array_paths=["missing"],
)
def test_loads_per_array_zarray_without_zmetadata(unconsolidated_zarrv2_store):
datasource = zarrv2_datasource.ZarrV2Datasource(
str(unconsolidated_zarrv2_store),
array_paths=["images", "nested"],
)
assert set(datasource._metadata_by_path) == {"images", "nested"}
def test_full_scan_discovers_arrays_without_zmetadata(unconsolidated_zarrv2_store):
datasource = zarrv2_datasource.ZarrV2Datasource(
str(unconsolidated_zarrv2_store),
allow_full_metadata_scan=True,
)
assert set(datasource._metadata_by_path) == {"images", "nested"}
def test_requires_array_paths_or_full_scan_when_unconsolidated(
unconsolidated_zarrv2_store,
):
with pytest.raises(
ValueError,
match=(
r"No array_paths were provided and this Zarr store does not "
r"contain \.zmetadata"
),
):
zarrv2_datasource.ZarrV2Datasource(str(unconsolidated_zarrv2_store))
def test_array_paths_missing_zarray_file_raises_value_error(
unconsolidated_zarrv2_store,
):
with pytest.raises(
ValueError,
match=r"Array path 'missing' not found",
):
zarrv2_datasource.ZarrV2Datasource(
str(unconsolidated_zarrv2_store),
array_paths=["missing"],
)
def test_local_scheme_pins_reads_to_driver_node(zarrv2_group_store):
"""``local://`` stores can't be read distributed; plain/cloud paths can."""
local = zarrv2_datasource.ZarrV2Datasource("local://" + str(zarrv2_group_store))
assert local.supports_distributed_reads is False
plain = zarrv2_datasource.ZarrV2Datasource(str(zarrv2_group_store))
assert plain.supports_distributed_reads is True
def test_consolidation_detected_via_open_consolidated(
zarrv2_group_store, unconsolidated_zarrv2_store
):
"""``_consolidated`` reflects whether ``.zmetadata`` actually opened."""
consolidated = zarrv2_datasource.ZarrV2Datasource(
str(zarrv2_group_store), array_paths=["images"]
)
assert consolidated._consolidated is True
unconsolidated = zarrv2_datasource.ZarrV2Datasource(
str(unconsolidated_zarrv2_store), array_paths=["images"]
)
assert unconsolidated._consolidated is False
def test_array_paths_rejects_group_path(tmp_path):
"""Requesting a group path (not an array) on an unconsolidated store errors."""
store_path = tmp_path / "withgroup.zarr"
root = zarr.open_group(str(store_path), mode="w")
grp = root.create_group("grp")
grp.create_dataset("inner", data=np.arange(4, dtype="<i4"), chunks=(2,))
# Not consolidated -> the per-array ``.zarray`` lookup path.
with pytest.raises(ValueError, match="is a group, not an array"):
zarrv2_datasource.ZarrV2Datasource(str(store_path), array_paths=["grp"])
def test_root_array_rejects_non_root_array_paths(zarrv2_root_store):
"""A single root-level array rejects array_paths that aren't the root ''."""
with pytest.raises(ValueError, match="single root-level array"):
zarrv2_datasource.ZarrV2Datasource(
str(zarrv2_root_store), array_paths=["missing"]
)
# ---------------------------------------------------------------------------
# chunk_shapes validation
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"chunk_shapes, match",
[
("invalid", "positive integers"),
({"images": 1}, "positive integers"),
({"does_not_exist": [2]}, "Unknown array path"),
],
)
def test_rejects_invalid_chunk_shapes(zarrv2_group_store, chunk_shapes, match):
with pytest.raises(ValueError, match=match):
zarrv2_datasource.ZarrV2Datasource(
str(zarrv2_group_store), chunk_shapes=chunk_shapes
)
@pytest.mark.parametrize(
"chunk_shapes,array_paths,expected",
[
# No chunk_shapes: every array reads at its native chunk size.
# 4-D image with tiny chunks coexists with 2-D pose with big chunks —
# nothing is forced into a shared min/max.
(
None,
None,
{
"data/camera0_rgb": (1, 2, 2, 3),
"data/robot0_eef_pos": (10, 3),
"meta/episode_ends": (3,),
},
),
# ``[5]`` prefix overrides axis 0 across arrays of all ranks at once.
(
[5],
None,
{
"data/camera0_rgb": (5, 2, 2, 3),
"data/robot0_eef_pos": (5, 3),
"meta/episode_ends": (5,),
},
),
# Length-2 prefix overrides axes 0+1; needs every selected array to
# have rank >= 2, so we filter out ``meta/episode_ends`` (rank 1).
(
[5, 1],
["data/camera0_rgb", "data/robot0_eef_pos"],
{
"data/camera0_rgb": (5, 1, 2, 3),
"data/robot0_eef_pos": (5, 1),
},
),
# Per-array overrides may retile only some arrays while others keep
# their native chunks.
(
{
"data/camera0_rgb": [5],
"data/robot0_eef_pos": [7],
},
None,
{
"data/camera0_rgb": (5, 2, 2, 3),
"data/robot0_eef_pos": (7, 3),
"meta/episode_ends": (3,),
},
),
],
)
def test_chunk_shapes_resolution_across_mixed_rank(
heterogeneous_zarrv2_store, chunk_shapes, array_paths, expected
):
datasource = zarrv2_datasource.ZarrV2Datasource(
str(heterogeneous_zarrv2_store),
chunk_shapes=chunk_shapes,
array_paths=array_paths,
)
assert datasource._array_chunks == expected
# ---------------------------------------------------------------------------
# align_axis_0 (wide-form mode)
# ---------------------------------------------------------------------------
def test_align_axis_0_emits_wide_rows(ray_start_regular_shared, aligned_zarrv2_store):
"""Wide-row schema: ``t_start``, ``t_stop``, one column per selected array."""
datasource = zarrv2_datasource.ZarrV2Datasource(
str(aligned_zarrv2_store),
align_axis_0=True,
chunk_shapes=[4],
)
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=4))
assert set(df.columns) == {"t_start", "t_stop", "img", "state", "label"}
# shape[0]=8, chunk_shapes=[4] -> 2 rows.
assert len(df) == 2
# Reconstruct each array by concatenating slices in order.
img_recon = np.concatenate(list(df["img"]), axis=0)
assert img_recon.shape == (8, 4, 4, 3)
state_recon = np.concatenate(list(df["state"]), axis=0)
assert state_recon.shape == (8, 3)
label_recon = np.concatenate(list(df["label"]), axis=0)
assert label_recon.shape == (8,)
# t_start/t_stop are correct.
starts = sorted(df["t_start"].tolist())
stops = sorted(df["t_stop"].tolist())
assert starts == [0, 4]
assert stops == [4, 8]
def test_align_axis_0_column_set(ray_start_regular_shared, aligned_zarrv2_store):
"""array_paths selects which arrays are read; aligned mode emits one column
per selected array (plus t_start/t_stop)."""
datasource = zarrv2_datasource.ZarrV2Datasource(
str(aligned_zarrv2_store),
array_paths=["img", "state"],
align_axis_0=True,
chunk_shapes=[4],
)
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=4))
assert set(df.columns) == {"t_start", "t_stop", "img", "state"}
def test_align_axis_0_rejects_misaligned_shape0(heterogeneous_zarrv2_store):
"""Misalignment raises with the per-array shape[0] breakdown."""
with pytest.raises(
ValueError,
match=r"All selected arrays must share shape\[0\]",
):
zarrv2_datasource.ZarrV2Datasource(
str(heterogeneous_zarrv2_store),
align_axis_0=True,
chunk_shapes=[5],
)
def test_align_axis_0_rejects_divergent_axis_0_chunks(aligned_zarrv2_store):
"""If aligned arrays end up with different axis-0 chunks, error clearly.
The native chunks differ (img=2, state=4, label=8) — without a
``chunk_shapes`` re-tile they all stay at native, and the validator
catches the mismatch.
"""
with pytest.raises(
ValueError, match="Aligned arrays must share the same axis-0 chunk size"
):
zarrv2_datasource.ZarrV2Datasource(
str(aligned_zarrv2_store),
align_axis_0=True,
)
# ---------------------------------------------------------------------------
# overlap (aligned-mode lookahead)
# ---------------------------------------------------------------------------
def test_overlap_extends_chunk_data(ray_start_regular_shared, aligned_zarrv2_store):
"""``overlap=N`` makes each row's per-array slice cover ``N`` extra timesteps.
Aligned store has shape[0]=8, ``chunk_shapes=[4]`` -> rows own [0,4) and [4,8).
With ``overlap=2``, row 0's data covers [0,6) and row 1's data covers [4,8)
(clipped at the store end since 4+4+2 > 8).
"""
datasource = zarrv2_datasource.ZarrV2Datasource(
str(aligned_zarrv2_store),
align_axis_0=True,
chunk_shapes=[4],
overlap=2,
)
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=4))
# Ownership unchanged: 2 rows of width 4 each.
assert sorted(zip(df["t_start"], df["t_stop"])) == [(0, 4), (4, 8)]
# Data extents: row 0 has 6 timesteps, row 1 has 4 (clipped at shape[0]=8).
rows = sorted(df.to_dict("records"), key=lambda r: r["t_start"])
assert rows[0]["img"].shape[0] == 6 # 4 owned + 2 overlap
assert rows[0]["state"].shape[0] == 6
assert rows[1]["img"].shape[0] == 4 # 4 owned + 0 overlap (clipped)
assert rows[1]["state"].shape[0] == 4
def test_overlap_requires_align_axis_0(aligned_zarrv2_store):
"""``overlap`` in long-form (no ``align_axis_0``) is a clear error."""
with pytest.raises(ValueError, match="overlap requires align_axis_0=True"):
zarrv2_datasource.ZarrV2Datasource(
str(aligned_zarrv2_store),
overlap=2,
)
def test_overlap_rejects_negative_and_non_int(aligned_zarrv2_store):
bad_values: list[Any] = [-1, 1.5, "two"]
for bad in bad_values:
with pytest.raises(ValueError, match="overlap must be a non-negative integer"):
zarrv2_datasource.ZarrV2Datasource(
str(aligned_zarrv2_store),
align_axis_0=True,
chunk_shapes=[4],
overlap=bad,
)
def test_chunk_shapes_rejected_when_longer_than_smallest_array(
heterogeneous_zarrv2_store,
):
"""A shared ``chunk_shapes`` override longer than a target rank is an error."""
with pytest.raises(
ValueError,
match=r"chunk_shapes override for array .* has 2 axes but array of shape .* has rank 1",
):
zarrv2_datasource.ZarrV2Datasource(
str(heterogeneous_zarrv2_store),
chunk_shapes=[2, 2], # OK for 2-D and 4-D, fails for 1-D episode_ends
)
# ---------------------------------------------------------------------------
# Filesystem handling
# ---------------------------------------------------------------------------
def test_accepts_pyarrow_fs_filesystem(zarrv2_group_store):
"""A pyarrow.fs.FileSystem passed in is wrapped into fsspec internally."""
datasource = zarrv2_datasource.ZarrV2Datasource(
str(zarrv2_group_store),
filesystem=pyarrow.fs.LocalFileSystem(),
)
from fsspec.spec import AbstractFileSystem
assert isinstance(datasource._fs, AbstractFileSystem)
assert set(datasource._metadata_by_path) == {"images", "nested"}
def test_rejects_unsupported_filesystem_type():
"""Filesystem that's neither pyarrow.fs nor fsspec raises ``TypeError``."""
with pytest.raises(
TypeError,
match=r"filesystem must be pyarrow\.fs\.FileSystem or",
):
zarrv2_datasource.ZarrV2Datasource(
"/tmp/some.zarr",
filesystem="not-a-filesystem",
)
# ---------------------------------------------------------------------------
# .zarr.zip URL support
# ---------------------------------------------------------------------------
def test_reads_zarr_zip_local_path(ray_start_regular_shared, zarr_zip_store):
"""A local ``.zarr.zip`` path auto-wires fsspec's ZipFileSystem."""
datasource = zarrv2_datasource.ZarrV2Datasource(str(zarr_zip_store))
# The store has one array "data" of shape (6, 2) chunks (3, 2) -> 2 chunks.
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=2))
assert len(df) == 2
assert set(df["array"]) == {"data"}
recon = _reconstruct_array(df, "data")
np.testing.assert_array_equal(recon, np.arange(12, dtype="<i4").reshape(6, 2))
# ---------------------------------------------------------------------------
# Read task generation and execution (end-to-end)
# ---------------------------------------------------------------------------
def test_get_read_tasks_batches_chunks_by_parallelism(tmp_path):
"""5 chunks split across parallelism=3 produces batches [2, 2, 1]."""
store_path = tmp_path / "store.zarr"
_write_real_zarr_store(
store_path,
{"images": (np.arange(5 * 4, dtype="<i4").reshape(5, 4), (1, 4))},
)
datasource = zarrv2_datasource.ZarrV2Datasource(str(store_path))
read_tasks = datasource.get_read_tasks(parallelism=3)
assert len(read_tasks) == 3
assert [task.metadata.num_rows for task in read_tasks] == [2, 2, 1]
assert all(task.metadata.input_files == (str(store_path),) for task in read_tasks)
def test_long_form_chunk_index_order_matches_grid(ray_start_regular_shared, tmp_path):
"""Lazy grid-range tasks emit chunk_index in the same row-major order as a
full grid enumeration (regression guard for the lazy-unravel refactor)."""
from itertools import product
store_path = tmp_path / "order.zarr"
# shape (6, 4), chunks (2, 2) -> grid (3, 2) = 6 chunks.
_write_real_zarr_store(
store_path, {"a": (np.arange(6 * 4, dtype="<i4").reshape(6, 4), (2, 2))}
)
datasource = zarrv2_datasource.ZarrV2Datasource(str(store_path))
# parallelism=2 -> two flat-index ranges; concatenated they must be in order.
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=2))
got = [tuple(int(x) for x in ci) for ci in df["chunk_index"]]
assert got == list(product(range(3), range(2)))
def test_per_task_row_limit_caps_chunks_read(
ray_start_regular_shared, tmp_path, monkeypatch
):
"""per_task_row_limit bounds how many chunks a task actually reads, so a
downstream ``limit`` doesn't pull the whole batch's I/O."""
store_path = tmp_path / "limit.zarr"
_write_real_zarr_store(store_path, {"data": (np.arange(10, dtype="<i4"), (1,))})
datasource = zarrv2_datasource.ZarrV2Datasource(str(store_path))
reads = []
real_read_chunk = zarrv2_datasource._read_chunk
def _spy(*args, **kwargs):
reads.append(1)
return real_read_chunk(*args, **kwargs)
monkeypatch.setattr(zarrv2_datasource, "_read_chunk", _spy)
# parallelism=1 -> one task batching all 10 chunks; cap it at 3.
tasks = datasource.get_read_tasks(parallelism=1, per_task_row_limit=3)
blocks = [block for task in tasks for block in task()]
total_rows = sum(BlockAccessor.for_block(b).num_rows() for b in blocks)
assert total_rows == 3
# The fix: only 3 chunks were actually read (not all 10, then truncated).
assert len(reads) == 3
def test_read_chunk_retries_transient_io(monkeypatch):
"""_read_chunk retries reads whose error matches retry_match (Ray Data's
DataContext.retried_io_errors), then succeeds."""
monkeypatch.setattr("time.sleep", lambda *_: None) # no backoff in the test
class _FlakyArray:
attempts = 0
def __getitem__(self, _idx):
type(self).attempts += 1
if self.attempts < 3:
raise OSError("Connection reset by peer")
return np.arange(4, dtype="<i4")
class _Root:
def __getitem__(self, _name):
return _FlakyArray()
out = zarrv2_datasource._read_chunk(
_Root(), # pyrefly: ignore[bad-argument-type]
"x",
((0, 4),),
retry_match=["Connection reset"],
)
np.testing.assert_array_equal(out, np.arange(4, dtype="<i4"))
assert _FlakyArray.attempts == 3 # failed twice, then succeeded
def test_long_form_schema_and_materialization(ray_start_regular_shared, tmp_path):
"""End-to-end: long-form rows are emitted with the expected columns and data."""
store_path = tmp_path / "aligned.zarr"
images_src = np.arange(20, dtype="<i4").reshape(5, 4)
labels_src = np.arange(5, dtype="|u1")
_write_real_zarr_store(
store_path,
{
"images": (images_src, (2, 4)),
"labels": (labels_src, (2,)),
},
)
datasource = zarrv2_datasource.ZarrV2Datasource(str(store_path))
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=16))
# Schema is the long-form quad.
assert list(df.columns) == ["array", "chunk_index", "chunk_slices", "chunk"]
# 3 chunks for images (5/2), 3 chunks for labels (5/2) = 6 rows total.
assert len(df) == 6
assert set(df["array"]) == {"images", "labels"}
np.testing.assert_array_equal(_reconstruct_array(df, "images"), images_src)
np.testing.assert_array_equal(_reconstruct_array(df, "labels"), labels_src)
# ``chunk_slices`` matches the actual chunk shape and indexes back to
# the source array: arr[start:stop, ...] equals the chunk.
for _, row in df.iterrows():
slices = row["chunk_slices"]
chunk = row["chunk"]
assert len(slices) == chunk.ndim
for axis, (start, stop) in enumerate(slices):
assert stop - start == chunk.shape[axis]
if row["array"] == "images":
np.testing.assert_array_equal(
chunk,
images_src[slices[0][0] : slices[0][1], slices[1][0] : slices[1][1]],
)
def test_chunk_shapes_override_changes_grid(ray_start_regular_shared, tmp_path):
"""User-supplied chunk_shapes controls the chunk grid and row count."""
store_path = tmp_path / "tile.zarr"
src = np.arange(10, dtype="<i4")
_write_real_zarr_store(store_path, {"data": (src, (2,))}) # native: 5 chunks
datasource = zarrv2_datasource.ZarrV2Datasource(str(store_path), chunk_shapes=[5])
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=16))
assert sorted(chunk.shape[0] for chunk in df["chunk"]) == [5, 5]
def test_heterogeneous_store_emits_one_row_per_chunk(
ray_start_regular_shared, heterogeneous_zarrv2_store
):
"""Mixed-rank/shape/dtype arrays each contribute their chunk count to the output."""
datasource = zarrv2_datasource.ZarrV2Datasource(str(heterogeneous_zarrv2_store))
df = _execute_read_tasks(datasource.get_read_tasks(parallelism=16))
# Expected chunk counts:
# data/camera0_rgb shape=(20,2,2,3) chunks=(1,2,2,3) → 20 chunks
# data/robot0_eef_pos shape=(20,3) chunks=(10,3) → 2 chunks
# meta/episode_ends shape=(3,) chunks=(3,) → 1 chunk
counts = df.groupby("array").size().to_dict()
assert counts == {
"data/camera0_rgb": 20,
"data/robot0_eef_pos": 2,
"meta/episode_ends": 1,
}
# ---------------------------------------------------------------------------
# Estimator
# ---------------------------------------------------------------------------
def test_estimate_inmemory_data_size(tmp_path):
"""Estimate = sum over arrays of numel * dtype.itemsize."""
store_path = tmp_path / "est.zarr"
_write_real_zarr_store(
store_path,
{
"a": (np.zeros((5, 4), dtype="<i4"), (2, 4)),
"b": (np.zeros(5, dtype="|u1"), (2,)),
},
)
datasource = zarrv2_datasource.ZarrV2Datasource(str(store_path))
# 5*4*4 (a) + 5*1 (b) = 80 + 5 = 85
assert datasource.estimate_inmemory_data_size() == 85
# ---------------------------------------------------------------------------
# Cross-filesystem end-to-end (Ray Data convention)
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"fs",
[
None,
lazy_fixture("local_fs"), # pyarrow.fs (gets wrapped to fsspec internally)
lazy_fixture("local_fsspec_fs"), # native fsspec
],
)
def test_read_zarr_basic_across_filesystems(ray_start_regular_shared, fs, local_path):
"""Round-trip a real Zarr store through read_zarr for each filesystem flavor.
Mirrors the parametrized read-path coverage other Ray Data datasources use
(lance, parquet, json, hudi, …) — exercises None / pyarrow.fs / fsspec
input shapes against the same store written to a local path.
"""
store_path = os.path.join(local_path, "data.zarr")
images_src = np.arange(20, dtype="<i4").reshape(5, 4)
labels_src = np.arange(5, dtype="|u1")
_write_real_zarr_store(
Path(store_path),
{
"images": (images_src, (2, 4)),
"labels": (labels_src, (2,)),
},
)
ds = ray.data.read_zarr(store_path, filesystem=fs)
# 3 chunks each for images and labels (5/2 → ceil = 3) → 6 rows total.
assert ds.count() == 6
df = pd.DataFrame(ds.take_all())
np.testing.assert_array_equal(_reconstruct_array(df, "images"), images_src)
np.testing.assert_array_equal(_reconstruct_array(df, "labels"), labels_src)
def test_rejects_zarr_v3(tmp_path, monkeypatch):
"""read_zarr targets zarr-python 2.x; an incompatible v3 install must raise a
clear, actionable error at construction, not a cryptic ImportError mid-read."""
monkeypatch.setattr(zarr, "__version__", "3.0.1")
with pytest.raises(ImportError, match=r"zarr-python 2\.x"):
zarrv2_datasource.ZarrV2Datasource(str(tmp_path))
def test_explicit_filesystem_strips_uri_scheme(ray_start_regular_shared, tmp_path):
"""An explicit ``filesystem=`` plus a scheme-prefixed path must strip the
scheme so the store path is backend-relative. Regression: pyarrow
filesystems can't resolve a ``file://`` / ``gs://`` prefix in the path."""
store_path = tmp_path / "scheme.zarr"
_write_real_zarr_store(store_path, {"data": (np.arange(6, dtype="<i4"), (2,))})
ds = zarrv2_datasource.ZarrV2Datasource(
f"file://{store_path}", filesystem=pyarrow.fs.LocalFileSystem()
)
assert ds._store_path == str(store_path)
df = _execute_read_tasks(ds.get_read_tasks(parallelism=2))
assert len(df) == 3
def test_get_read_tasks_parallelism_zero(tmp_path):
"""parallelism=0 must not divide by zero; fall back to a single task."""
store_path = tmp_path / "p0.zarr"
_write_real_zarr_store(store_path, {"data": (np.arange(10, dtype="<i4"), (2,))})
ds = zarrv2_datasource.ZarrV2Datasource(str(store_path))
tasks = ds.get_read_tasks(parallelism=0)
assert len(tasks) >= 1
def test_align_axis_0_rejects_scalar_array(tmp_path):
"""align_axis_0=True with a 0-D (scalar) array must raise a clear error
rather than an IndexError when reading the (empty) axis-0 chunk size."""
store_path = tmp_path / "scalar.zarr"
root = zarr.open_group(str(store_path), mode="w")
root.create_dataset("vec", data=np.arange(8, dtype="<i4"), chunks=(4,))
root.create_dataset("scalar", data=np.array(42, dtype="<i4")) # 0-D
zarr.consolidate_metadata(zarr.DirectoryStore(str(store_path)))
with pytest.raises(ValueError, match=r"0-D \(scalar\)"):
zarrv2_datasource.ZarrV2Datasource(str(store_path), align_axis_0=True)
def test_reads_zarr_zip_with_explicit_zip_filesystem(
ray_start_regular_shared, zarr_zip_store
):
"""A .zip path read through an explicitly-passed fsspec ZipFileSystem must
resolve the store at the archive root (store path ``""``), not treat the
``.zip`` name as an entry inside the archive."""
zip_fs = fsspec.filesystem("zip", fo=str(zarr_zip_store))
ds = zarrv2_datasource.ZarrV2Datasource(str(zarr_zip_store), filesystem=zip_fs)
assert ds._store_path == ""
df = _execute_read_tasks(ds.get_read_tasks(parallelism=2))
assert len(df) == 2
def test_align_axis_0_columns_unify_across_blocks(
ray_start_regular_shared, aligned_zarrv2_store
):
"""Wide-form gives each array its own column, so blocks combine cleanly
across the dataset even with trailing edge chunks of differing shape -- the
batch-safe schema for row-aligned arrays."""
from ray.data._internal.arrow_ops.transform_pyarrow import unify_schemas
from ray.data.block import BlockAccessor
ds = zarrv2_datasource.ZarrV2Datasource(
str(aligned_zarrv2_store), align_axis_0=True, chunk_shapes=[3]
)
blocks = [block for task in ds.get_read_tasks(parallelism=64) for block in task()]
assert len(blocks) > 1 # actually exercise cross-block unification
schemas = [BlockAccessor.for_block(b).to_arrow().schema for b in blocks]
unified = unify_schemas(schemas) # must not raise
assert {"t_start", "t_stop", "img", "state", "label"}.issubset(set(unified.names))
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", __file__]))
# ---------------------------------------------------------------------------
# Custom codec registration in Ray workers
# ---------------------------------------------------------------------------
@pytest.fixture
def fresh_ray():
"""A clean Ray for a test that needs its own ``ray.init`` (e.g. a custom
``runtime_env``). Unlike ``shutdown_only`` (teardown only), it also shuts
down any pre-existing cluster, so isolation doesn't depend on test order.
"""
if ray.is_initialized():
ray.shutdown()
yield
if ray.is_initialized():
ray.shutdown()
def test_custom_codec_succeeds_with_worker_setup_hook(fresh_ray, tmp_path):
"""Test that we successfully register a custom codec.
numcodecs' registry is process-local.
"""
import numcodecs
def _register_codec():
import numcodecs
import numpy as np
class _RayZarrTestCodec(numcodecs.abc.Codec):
codec_id = "ray_zarr_test_codec"
def encode(self, buf):
return bytes(buf)
def decode(self, buf, out=None):
arr = np.frombuffer(buf, dtype=np.uint8)
if out is not None:
out[:] = arr.view(out.dtype)
return out
return arr.copy()
numcodecs.register_codec(_RayZarrTestCodec)
# Register driver-side so we can write the store.
_register_codec()
store_path = tmp_path / "codec_test.zarr"
arr = zarr.open(
str(store_path),
mode="w",
shape=(8,),
chunks=(4,),
dtype="u1",
compressor=numcodecs.get_codec({"id": "ray_zarr_test_codec"}),
)
arr[:] = np.arange(8, dtype="u1")
zarr.consolidate_metadata(zarr.DirectoryStore(str(store_path)))
ray.init(
num_cpus=1,
logging_level=logging.ERROR,
log_to_driver=False,
runtime_env={"worker_process_setup_hook": _register_codec},
)
ds = ray.data.read_zarr(str(store_path))
rows = sorted(ds.take_all(), key=lambda r: tuple(r["chunk_index"]))
recon = np.concatenate([r["chunk"] for r in rows])
np.testing.assert_array_equal(recon, np.arange(8, dtype="u1"))