From 1e6e9a0ec2fb89b06a9c0b60c870325ce7b3c3a4 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Thu, 6 Aug 2026 10:18:29 -0400 Subject: [PATCH 1/5] bench: add read benchmark scaffold with the zarr-python row Adds bench/bench_read.py, which writes a fixture array with zarr-python and times full-array reads. This commit measures the stock zarr-python codec pipeline only; the zarrs and zarrista rows follow. Adds a bench dependency group and bench/* ruff ignores. Co-Authored-By: Claude Opus 5 (1M context) --- bench/bench_read.py | 240 ++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 14 +++ 2 files changed, 254 insertions(+) create mode 100644 bench/bench_read.py diff --git a/bench/bench_read.py b/bench/bench_read.py new file mode 100644 index 0000000..1366047 --- /dev/null +++ b/bench/bench_read.py @@ -0,0 +1,240 @@ +"""Compare full-array read speed across three Zarr implementations. + +The benchmark writes one array with zarr-python. It then reads the whole array +repeatedly with each implementation, and prints a comparison table. + +Build zarrista in release mode first. A debug build is many times slower, and +it makes the result meaningless: + + uv sync --group bench --no-install-package zarrista + uv run --no-project maturin develop --uv --release + +Then run the benchmark. Omit `--shards` to benchmark a plain chunked array: + + uv run --no-project python bench/bench_read.py + uv run --no-project python bench/bench_read.py --shards 512 512 +""" + +from __future__ import annotations + +import argparse +import shutil +import statistics +import tempfile +import time +from pathlib import Path +from typing import TYPE_CHECKING + +import numpy as np + +if TYPE_CHECKING: + from collections.abc import Callable + +ARRAY_NAME = "bench" +"""The name of the array inside the fixture store.""" + + +def build_data(shape: tuple[int, ...], dtype: str) -> np.ndarray: + """Build the deterministic source array that the benchmark writes and checks. + + Args: + shape: The array shape. + dtype: The NumPy data type name. + + Returns: + An array of the given shape and data type. + """ + count = int(np.prod(shape)) + return (np.arange(count) % 251).astype(dtype).reshape(shape) + + +def write_fixture( + root: Path, + data: np.ndarray, + chunks: tuple[int, ...], + shards: tuple[int, ...] | None, + compressor: str, +) -> None: + """Write the benchmark array once, with zarr-python. + + Every implementation then reads these same bytes. + + Args: + root: The directory that holds the store. + data: The data to write. + chunks: The chunk shape. This is the inner chunk shape when sharded. + shards: The shard shape, or `None` for a plain chunked array. + compressor: A blosc `cname`, or `"none"` for no compression. + """ + import zarr + from zarr.codecs import BloscCodec + + compressors = (BloscCodec(cname=compressor),) if compressor != "none" else None + array = zarr.create_array( + store=str(root), + name=ARRAY_NAME, + shape=data.shape, + chunks=chunks, + shards=shards, + dtype=data.dtype.name, + compressors=compressors, + ) + array[...] = data + + +def time_reads( + read: Callable[[], np.ndarray], + iterations: int, +) -> tuple[np.ndarray, list[float]]: + """Run one warm-up read, then time `iterations` more reads. + + Args: + read: A function that reads the whole array and returns it. + iterations: The number of timed reads. + + Returns: + The data from the last read, and the elapsed seconds of each timed read. + """ + out = read() + times: list[float] = [] + for _ in range(iterations): + start = time.perf_counter() + out = read() + times.append(time.perf_counter() - start) + return out, times + + +def run_zarr( + root: Path, + iterations: int, + overrides: dict[str, object], +) -> tuple[np.ndarray, list[float]]: + """Time full-array reads through zarr-python, under the given configuration. + + The array is opened inside the configuration scope, so that zarr-python + picks up the selected codec pipeline. + + Args: + root: The directory that holds the store. + iterations: The number of timed reads. + overrides: The `zarr.config` settings to apply for this run. + + Returns: + The data from the last read, and the elapsed seconds of each timed read. + """ + import zarr + + with zarr.config.set(overrides): + array = zarr.open_array(store=str(root), path=ARRAY_NAME) + + def read() -> np.ndarray: + return array[...] + + return time_reads(read, iterations) + + +def report( + results: list[tuple[str, list[float]]], + nbytes: int, + header: list[str], +) -> None: + """Print the header lines and the comparison table. + + The final column compares each implementation against the first one. + + Args: + results: The name and the timings of each implementation, in order. + nbytes: The logical size of the array in bytes. + header: The lines to print above the table. + """ + for line in header: + print(line) + print() + megabytes = nbytes / 1e6 + baseline = statistics.median(results[0][1]) + baseline_label = f"vs {results[0][0]}" + print( + f"{'implementation':<22}{'best (ms)':>12}{'median (ms)':>14}" + f"{'median MB/s':>14}{baseline_label:>18}", + ) + for name, times in results: + best = min(times) + median = statistics.median(times) + print( + f"{name:<22}{best * 1e3:>12.2f}{median * 1e3:>14.2f}" + f"{megabytes / median:>14.0f}{baseline / median:>17.2f}x", + ) + + +def parse_args() -> argparse.Namespace: + """Parse the command-line arguments. + + Returns: + The parsed arguments. + """ + parser = argparse.ArgumentParser(description="Compare full-array Zarr read speed.") + parser.add_argument("--shape", type=int, nargs="+", default=[2048, 2048]) + parser.add_argument( + "--chunks", + type=int, + nargs="+", + default=[64, 64], + help="chunk shape; the inner chunk shape when --shards is given", + ) + parser.add_argument( + "--shards", + type=int, + nargs="+", + default=None, + help="shard shape; omit for a plain chunked array", + ) + parser.add_argument("--dtype", default="uint16") + parser.add_argument("--iterations", type=int, default=10) + parser.add_argument( + "--compressor", + default="zstd", + choices=["zstd", "lz4", "none"], + help="blosc cname, or none for no compression", + ) + return parser.parse_args() + + +def main(args: argparse.Namespace) -> None: + """Write the fixture, time every implementation, and print the table. + + Args: + args: The parsed command-line arguments. + """ + shape = tuple(args.shape) + chunks = tuple(args.chunks) + shards = tuple(args.shards) if args.shards else None + data = build_data(shape, args.dtype) + + root = Path(tempfile.mkdtemp(prefix="zarrista-bench-")) + try: + write_fixture(root, data, chunks, shards, args.compressor) + + results: list[tuple[str, list[float]]] = [] + out, times = run_zarr(root, args.iterations, {}) + np.testing.assert_array_equal(out, data) + results.append(("zarr-python", times)) + + header = [ + ( + f"array: shape={shape} dtype={args.dtype} chunks={chunks} " + f"shards={shards} compressor={args.compressor}" + ), + ( + f"size: {data.nbytes / 1e6:.1f} MB logical," + f" {args.iterations} iterations, FilesystemStore" + ), + "correctness: all implementations match the source data", + "note: these numbers are valid only if zarrista was built with --release", + ] + report(results, data.nbytes, header) + finally: + shutil.rmtree(root, ignore_errors=True) + + +if __name__ == "__main__": + main(parse_args()) diff --git a/pyproject.toml b/pyproject.toml index d1f1e40..7aef8f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,15 @@ dev = [ "ruff>=0.16", "zarr>=3", ] +# Dependencies for the read benchmarks in `bench/`. Kept out of `dev` because +# `zarrs` is a second Rust extension that only the benchmarks need. +bench = [ + "numpy>=2.4.6", + "zarr>=3.1", + # The Rust codec pipeline plugin for zarr-python. It is a separate project + # from zarrista, and the benchmark compares against it. + "zarrs>=0.2.3", +] docs = [ # Workaround for https://github.com/mkdocs/mkdocs/issues/4032 "click<8.5", @@ -97,6 +106,11 @@ known-first-party = ["zarrista"] "*.pyi" = [ "A002", # Function argument `bytes` is shadowing a Python builtin ] +"bench/*" = [ + "INP001", # implicit namespace package (bench is a script dir, not a package) + "PLC0415", # import not at top-level (see the import-order note in bench_read.py) + "T201", # print (the benchmark reports its results on stdout) +] "tests/*" = [ "ANN001", # annotation in function argument "ANN201", # return type annotation From 4a7289c81142aabe8f116fb95cc77c02080131fb Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Thu, 6 Aug 2026 10:18:54 -0400 Subject: [PATCH 2/5] bench: add the zarr-python+zarrs row Runs the same read through the zarrs Rust codec pipeline plugin. This row separates the effect of Rust codecs from the effect of a native end-to-end binding, which the zarrista row measures next. Co-Authored-By: Claude Opus 5 (1M context) --- bench/bench_read.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bench/bench_read.py b/bench/bench_read.py index 1366047..5471941 100644 --- a/bench/bench_read.py +++ b/bench/bench_read.py @@ -219,6 +219,14 @@ def main(args: argparse.Namespace) -> None: np.testing.assert_array_equal(out, data) results.append(("zarr-python", times)) + out, times = run_zarr( + root, + args.iterations, + {"codec_pipeline.path": "zarrs.ZarrsCodecPipeline"}, + ) + np.testing.assert_array_equal(out, data) + results.append(("zarr-python+zarrs", times)) + header = [ ( f"array: shape={shape} dtype={args.dtype} chunks={chunks} " From 3deb164515bd545185ccf8682b3ee8d8a2649290 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Thu, 6 Aug 2026 10:24:12 -0400 Subject: [PATCH 3/5] bench: add the zarrista row, matched thread counts, and a click CLI Adds --threads, which pins zarr-python's threading.max_workers and rayon's RAYON_NUM_THREADS to the same value for every implementation. Rayon reads RAYON_NUM_THREADS only when it first builds its global pool, so the script sets the variable before it imports zarrista or zarrs. Every extension import is therefore function-local. Replaces argparse with click. A shape is now one comma-separated value, such as --shards 512,512. A click.ParamType converts and validates it, so the benchmark code only ever sees a valid shape. Co-Authored-By: Claude Opus 5 (1M context) --- bench/bench_read.py | 220 ++++++++++++++++++++++++++++++++------------ pyproject.toml | 1 + 2 files changed, 164 insertions(+), 57 deletions(-) diff --git a/bench/bench_read.py b/bench/bench_read.py index 5471941..f9c68a6 100644 --- a/bench/bench_read.py +++ b/bench/bench_read.py @@ -12,19 +12,20 @@ Then run the benchmark. Omit `--shards` to benchmark a plain chunked array: uv run --no-project python bench/bench_read.py - uv run --no-project python bench/bench_read.py --shards 512 512 + uv run --no-project python bench/bench_read.py --shards 512,512 """ from __future__ import annotations -import argparse +import os import shutil import statistics import tempfile import time from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal, cast +import click import numpy as np if TYPE_CHECKING: @@ -33,6 +34,51 @@ ARRAY_NAME = "bench" """The name of the array inside the fixture store.""" +Compressor = Literal["zstd", "lz4", "none"] +"""The compression choices that the benchmark accepts.""" + + +class ShapeParam(click.ParamType): + """A click parameter that holds a comma-separated list of sizes. + + The parameter converts `"512,512"` into `(512, 512)`. It rejects any value + that is not a list of positive integers, so that the benchmark receives an + already-valid shape. + """ + + name = "sizes" + + def convert( + self, + value: str | tuple[int, ...], + param: click.Parameter | None, + ctx: click.Context | None, + ) -> tuple[int, ...]: + """Convert a comma-separated string into a tuple of sizes. + + Args: + value: The text to convert, or an already-converted tuple. + param: The parameter that the value belongs to. + ctx: The click context. + + Returns: + The sizes along each dimension. + """ + if isinstance(value, tuple): + return value + try: + sizes = tuple(int(part) for part in value.split(",")) + except ValueError: + message = f"{value!r} is not a comma-separated list of integers" + self.fail(message, param, ctx) + if not sizes or any(size < 1 for size in sizes): + self.fail(f"{value!r} must hold only positive integers", param, ctx) + return sizes + + +SIZES = ShapeParam() +"""The shared instance of the comma-separated size parameter.""" + def build_data(shape: tuple[int, ...], dtype: str) -> np.ndarray: """Build the deterministic source array that the benchmark writes and checks. @@ -53,7 +99,7 @@ def write_fixture( data: np.ndarray, chunks: tuple[int, ...], shards: tuple[int, ...] | None, - compressor: str, + compressor: Compressor, ) -> None: """Write the benchmark array once, with zarr-python. @@ -128,11 +174,42 @@ def run_zarr( array = zarr.open_array(store=str(root), path=ARRAY_NAME) def read() -> np.ndarray: - return array[...] + # A full basic selection always gives an array, never a scalar, but + # the declared return type also covers the scalar case. + return cast("np.ndarray", array[...]) return time_reads(read, iterations) +def run_zarrista(root: Path, iterations: int) -> tuple[np.ndarray, list[float]]: + """Time full-array reads through zarrista. + + The `.to_numpy()` call is inside the timed region. zarrista returns a + `Tensor`, and the other implementations return a NumPy array, so this makes + every implementation produce the same result type. + + Args: + root: The directory that holds the store. + iterations: The number of timed reads. + + Returns: + The data from the last read, and the elapsed seconds of each timed read. + """ + import zarrista + from zarrista.store import FilesystemStore + + array = zarrista.Array.open(FilesystemStore(root), path=f"/{ARRAY_NAME}") + + def read() -> np.ndarray: + # The benchmark only uses fixed-width numeric data types, which always + # decode to a `Tensor`. The other members of `DecodedArray` cannot + # occur here, and one of them has no `to_numpy` method. + tensor = cast("zarrista.Tensor", array[...]) + return tensor.to_numpy() + + return time_reads(read, iterations) + + def report( results: list[tuple[str, list[float]]], nbytes: int, @@ -166,75 +243,104 @@ def report( ) -def parse_args() -> argparse.Namespace: - """Parse the command-line arguments. - - Returns: - The parsed arguments. - """ - parser = argparse.ArgumentParser(description="Compare full-array Zarr read speed.") - parser.add_argument("--shape", type=int, nargs="+", default=[2048, 2048]) - parser.add_argument( - "--chunks", - type=int, - nargs="+", - default=[64, 64], - help="chunk shape; the inner chunk shape when --shards is given", - ) - parser.add_argument( - "--shards", - type=int, - nargs="+", - default=None, - help="shard shape; omit for a plain chunked array", - ) - parser.add_argument("--dtype", default="uint16") - parser.add_argument("--iterations", type=int, default=10) - parser.add_argument( - "--compressor", - default="zstd", - choices=["zstd", "lz4", "none"], - help="blosc cname, or none for no compression", - ) - return parser.parse_args() - - -def main(args: argparse.Namespace) -> None: - """Write the fixture, time every implementation, and print the table. - - Args: - args: The parsed command-line arguments. - """ - shape = tuple(args.shape) - chunks = tuple(args.chunks) - shards = tuple(args.shards) if args.shards else None - data = build_data(shape, args.dtype) - +@click.command() +@click.option( + "--shape", + type=SIZES, + default="2048,2048", + show_default=True, + help="The array shape, as comma-separated sizes.", +) +@click.option( + "--chunks", + type=SIZES, + default="64,64", + show_default=True, + help="The chunk shape. This is the inner chunk shape when --shards is given.", +) +@click.option( + "--shards", + type=SIZES, + default=None, + help="The shard shape. Omit it for a plain chunked array.", +) +@click.option( + "--dtype", + default="uint16", + show_default=True, + help="The NumPy data type name.", +) +@click.option( + "--iterations", + type=int, + default=10, + show_default=True, + help="The number of timed reads for each implementation.", +) +@click.option( + "--compressor", + type=click.Choice(["zstd", "lz4", "none"]), + default="zstd", + show_default=True, + help="A blosc cname, or none for no compression.", +) +@click.option( + "--threads", + type=int, + default=os.cpu_count() or 1, + show_default="CPU count", + help="The thread count, applied to every implementation.", +) +def main( # noqa: PLR0913 + *, + shape: tuple[int, ...], + chunks: tuple[int, ...], + shards: tuple[int, ...] | None, + dtype: str, + iterations: int, + compressor: Compressor, + threads: int, +) -> None: + """Compare full-array read speed across three Zarr implementations.""" + # Rayon reads RAYON_NUM_THREADS once, when it first builds its global + # thread pool. zarrista and zarrs each hold a separate pool. Set the + # variable before either extension is imported, or --threads does nothing. + # This is why every extension import in this file is function-local. + os.environ["RAYON_NUM_THREADS"] = str(threads) + + data = build_data(shape, dtype) root = Path(tempfile.mkdtemp(prefix="zarrista-bench-")) try: - write_fixture(root, data, chunks, shards, args.compressor) + write_fixture(root, data, chunks, shards, compressor) + + max_workers = {"threading.max_workers": threads} results: list[tuple[str, list[float]]] = [] - out, times = run_zarr(root, args.iterations, {}) + out, times = run_zarr(root, iterations, max_workers) np.testing.assert_array_equal(out, data) results.append(("zarr-python", times)) out, times = run_zarr( root, - args.iterations, - {"codec_pipeline.path": "zarrs.ZarrsCodecPipeline"}, + iterations, + {**max_workers, "codec_pipeline.path": "zarrs.ZarrsCodecPipeline"}, ) np.testing.assert_array_equal(out, data) results.append(("zarr-python+zarrs", times)) + out, times = run_zarrista(root, iterations) + np.testing.assert_array_equal(out, data) + results.append(("zarrista", times)) + header = [ ( - f"array: shape={shape} dtype={args.dtype} chunks={chunks} " - f"shards={shards} compressor={args.compressor}" + f"array: shape={shape} dtype={dtype} chunks={chunks} " + f"shards={shards} compressor={compressor}" ), ( f"size: {data.nbytes / 1e6:.1f} MB logical," - f" {args.iterations} iterations, FilesystemStore" + f" {iterations} iterations, FilesystemStore," + f" {threads} threads" ), "correctness: all implementations match the source data", "note: these numbers are valid only if zarrista was built with --release", @@ -245,4 +351,4 @@ def main(args: argparse.Namespace) -> None: if __name__ == "__main__": - main(parse_args()) + main() diff --git a/pyproject.toml b/pyproject.toml index 7aef8f1..532c0dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ dev = [ # Dependencies for the read benchmarks in `bench/`. Kept out of `dev` because # `zarrs` is a second Rust extension that only the benchmarks need. bench = [ + "click>=8", "numpy>=2.4.6", "zarr>=3.1", # The Rust codec pipeline plugin for zarr-python. It is a separate project From 02174ada0e078e7319fc5e20511e301895ff9066 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Thu, 6 Aug 2026 10:25:30 -0400 Subject: [PATCH 4/5] bench: document the read benchmarks Records how to build and run them, what each row measures, and real output at the default parameters. States the release-build requirement first, because a debug build makes every number meaningless. Co-Authored-By: Claude Opus 5 (1M context) --- bench/README.md | 136 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 bench/README.md diff --git a/bench/README.md b/bench/README.md new file mode 100644 index 0000000..357405a --- /dev/null +++ b/bench/README.md @@ -0,0 +1,136 @@ +# Read benchmarks + +These scripts answer one question: **is the native zarrs binding faster than +zarr-python?** + +They are for evaluation. There is no CI integration and no regression +tracking. Run them by hand, and read the numbers as evidence, not as a +contract. + +## Build zarrista in release mode first + +**The numbers mean nothing unless zarrista is built with `--release`.** The +normal development loop builds in debug mode, which turns off optimization in +the Rust code. A debug build is many times slower than a release build. + +Python cannot tell the two builds apart at run time, so the benchmark cannot +check this for you. + +```bash +uv sync --group bench --no-install-package zarrista +uv run --no-project maturin develop --uv --release +``` + +When you go back to normal development, build a debug version again: + +```bash +uv run --no-project maturin develop --uv +``` + +## Run + +Benchmark a sharded array: + +```bash +uv run --no-project python bench/bench_read.py --shards 512,512 +``` + +Benchmark a plain chunked array. Omit `--shards`: + +```bash +uv run --no-project python bench/bench_read.py +``` + +A shape is one comma-separated value, such as `--shards 512,512`. + +| Option | Default | Meaning | +| --- | --- | --- | +| `--shape` | `2048,2048` | The array shape. | +| `--chunks` | `64,64` | The chunk shape. This is the inner chunk shape when sharded. | +| `--shards` | none | The shard shape. Omit it for a plain chunked array. | +| `--dtype` | `uint16` | The NumPy data type name. | +| `--iterations` | `10` | The number of timed reads for each implementation. | +| `--compressor` | `zstd` | A blosc `cname`, or `none` for no compression. | +| `--threads` | CPU count | The thread count, applied to every implementation. | + +## What it measures + +The benchmark writes one array with zarr-python. Every implementation then +reads those same bytes from a local filesystem store. + +| Row | What it is | +| --- | --- | +| `zarr-python` | Stock zarr-python with the pure-Python codec pipeline. | +| `zarr-python+zarrs` | zarr-python with the `zarrs` Rust codec pipeline plugin. | +| `zarrista` | zarrista's `array[...]`, then `.to_numpy()`. | + +The middle row matters. Without it, you cannot tell whether a speed increase +comes from Rust codecs or from a native end-to-end binding. + +Each implementation does one read that is checked against the source data, +then one warm-up read, then the timed reads. The `.to_numpy()` call is inside +zarrista's timed region, because the other two rows already return a NumPy +array. + +## Example output + +Measured on an Apple M-series laptop, with 10 threads and a release build. +Your numbers will differ. Read the ratios, not the absolute times. + +Sharded, `--shards 512,512`: + +``` +array: shape=(2048, 2048) dtype=uint16 chunks=(64, 64) shards=(512, 512) compressor=zstd +size: 8.4 MB logical, 10 iterations, FilesystemStore, 10 threads +correctness: all implementations match the source data +note: these numbers are valid only if zarrista was built with --release + +implementation best (ms) median (ms) median MB/s vs zarr-python +zarr-python 81.97 83.49 100 1.00x +zarr-python+zarrs 2.60 2.66 3157 31.42x +zarrista 2.34 2.40 3502 34.86x +``` + +Plain chunked: + +``` +array: shape=(2048, 2048) dtype=uint16 chunks=(64, 64) shards=None compressor=zstd +size: 8.4 MB logical, 10 iterations, FilesystemStore, 10 threads +correctness: all implementations match the source data +note: these numbers are valid only if zarrista was built with --release + +implementation best (ms) median (ms) median MB/s vs zarr-python +zarr-python 193.82 197.89 42 1.00x +zarr-python+zarrs 21.28 21.61 388 9.16x +zarrista 10.78 11.19 749 17.68x +``` + +Both Rust rows are far ahead of the pure-Python row. The two Rust rows are +close on the sharded array, but zarrista is about twice as fast as the `zarrs` +codec pipeline on the plain chunked array. This is the result that the middle +row exists to show: on this shape, the gain comes from more than the codecs +alone. + +Treat one pair of runs as a starting point, not as a conclusion. Vary the +shape, the chunk shape, the data type, and the thread count before you trust a +ratio. + +## What is not controlled + +- **Store request concurrency.** zarr-python's `async.concurrency` setting + stays at its default of 10. It limits concurrent store requests, which is an + IO axis and not a CPU axis. `--threads` does not change it. +- **The page cache.** The fixture is written and then read immediately, so the + data is usually warm in the operating system page cache. The benchmark + measures decode speed much more than it measures disk speed. +- **Other work on the machine.** Close other programs before you trust a + result. + +## Not covered yet + +- An in-memory store. `zarrista.MemoryStore()` has no API that accepts + external bytes, so the two libraries cannot read one set of bytes from + memory. Each would have to write its own copy. +- Partial and strided region reads. +- Writes. +- Remote object stores. From 635e9ceefb17f55c532f036d06bcdb016e72c66e Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Thu, 6 Aug 2026 10:25:51 -0400 Subject: [PATCH 5/5] bench: lock the bench dependency group Adds zarrs to uv.lock, so that the lockfile matches the bench group in pyproject.toml. click was already locked for the docs group. Co-Authored-By: Claude Opus 5 (1M context) --- uv.lock | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/uv.lock b/uv.lock index 27e2be8..e84f59b 100644 --- a/uv.lock +++ b/uv.lock @@ -2268,6 +2268,12 @@ dependencies = [ ] [package.dev-dependencies] +bench = [ + { name = "numpy" }, + { name = "zarr", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "zarr", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "zarrs" }, +] dev = [ { name = "arro3-core" }, { name = "icechunk", version = "1.1.21", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, @@ -2302,6 +2308,11 @@ pyodide = [ requires-dist = [{ name = "zarr-metadata", specifier = ">=0.4" }] [package.metadata.requires-dev] +bench = [ + { name = "numpy", specifier = ">=2.4.6" }, + { name = "zarr", specifier = ">=3.1" }, + { name = "zarrs", specifier = ">=0.2.3" }, +] dev = [ { name = "arro3-core", specifier = ">=0.6" }, { name = "icechunk", marker = "python_full_version < '3.12'", specifier = "<2" }, @@ -2330,3 +2341,26 @@ pyodide = [ { name = "pyodide-build", marker = "python_full_version >= '3.12'", specifier = ">=0.35.0" }, { name = "pyodide-cli", marker = "python_full_version >= '3.12'", specifier = ">=0.2.2" }, ] + +[[package]] +name = "zarrs" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "zarr", version = "3.1.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "zarr", version = "3.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/b3/9e088d4ab5c971e5d2b52cd4d58e3acce35acb3e131990fdc28b69366233/zarrs-0.2.3.tar.gz", hash = "sha256:61640dbbffb9a0b0ebd73f970ce97b52ef56df2828c2809058016d76da59ee60", size = 64827, upload-time = "2026-03-27T08:47:44.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/c0/e10e618293351247e948527c0d2b4c3d8fa9f7478e9f8e945755fc47ecdc/zarrs-0.2.3-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b9470b17629961badf4261fb0d26ad5fcbe316b63c1b00fb0489a51c3f8ef157", size = 6276814, upload-time = "2026-03-27T08:47:24.992Z" }, + { url = "https://files.pythonhosted.org/packages/80/ad/8a8525a72190db2c8d6807c69695ef0ea959fd50a4ac887af80803ff5487/zarrs-0.2.3-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:e6998bf1a61cd7c4afd3c263130317c1001599b37ff6f27082cc900a0ad48baa", size = 5776732, upload-time = "2026-03-27T08:47:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/56/63/27f9f7784006a900ffaa3d62d5c4d0dde98821683cd298cad79f66aa25c5/zarrs-0.2.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:19b194f80139b838bb4bf18ed6ef93ecb1904717a04695fbc50cdc0c6074f282", size = 6139081, upload-time = "2026-03-27T08:47:28.465Z" }, + { url = "https://files.pythonhosted.org/packages/59/a9/28b91493c7db9f3db191a1bc396cd2e212559536f2bc7325e5d5cdbb8b53/zarrs-0.2.3-cp311-abi3-manylinux_2_28_armv7l.whl", hash = "sha256:59a29dfdea088bb25c1e9b5107cbb8de15c8d571d51484ff128cd526c40521b9", size = 5966557, upload-time = "2026-03-27T08:47:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c1/0aba516796af22be08e82e37ded59f46cc7ffabf6932957455fccb9c6109/zarrs-0.2.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:1d387b75a19c31795cb2a81ef973c905c2c04ca3b1a4cca4bc84c81050974827", size = 6736692, upload-time = "2026-03-27T08:47:31.988Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/471e2511b0c77419ac2228ce72770e94e994ab99c6b9275cb3de1dcead2d/zarrs-0.2.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ab4074056f01f3292c89cd769e8c0db92c0df076e3d36665eec7fc557a62a2ed", size = 6467125, upload-time = "2026-03-27T08:47:34.107Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6a/7a4230676bd66c0181b4e9000bec30deee1b1695557e5d245514f0454103/zarrs-0.2.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9db202e95c3b5c9116afdfebf9912e1faa5ab60e6a1982e0406953cdb47bec38", size = 12507436, upload-time = "2026-03-27T08:47:36.138Z" }, + { url = "https://files.pythonhosted.org/packages/e3/85/7ad323d428540ca7add343ade347841d181e4e3d73a69f39e34e447f0acc/zarrs-0.2.3-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:991556a7589e93bc5445da2b97e0c89d7d871e539b9ef28dae857b8573c65f5c", size = 12209703, upload-time = "2026-03-27T08:47:38.619Z" }, + { url = "https://files.pythonhosted.org/packages/6c/90/ca544236092ab4803d1c3c88ac7b143885e280a63954d454d60885784af8/zarrs-0.2.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:437dd4fcf74607480361f401f15b47416aa69f0ff4379c4ea330c453b7e05098", size = 13044036, upload-time = "2026-03-27T08:47:40.589Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c1/be4e37d80a95347334c287cbb42d94c6181d447f1624c0c5354f593e1fda/zarrs-0.2.3-cp311-abi3-win_amd64.whl", hash = "sha256:72eb1f5c4ca8382cb9e38dd98a48a0e484170d703152110f32a39520c7fa570d", size = 5854312, upload-time = "2026-03-27T08:47:42.856Z" }, +]