Readers¶
An IndexTransform defines which source value belongs at every result
position. A Reader defines how a particular backend obtains those values.
Readers do not define indexing semantics, partitioning, scheduling, or result
ownership.
Reader.read_into(source, context, out) receives a ReadContext whose
transform maps zero-origin output-buffer coordinates to global coordinates in
source, with context.transform.domain.shape == out.shape. Its optional
projection is the existing plan for a partitioned read. The projection's
chunk_transform remains chunk-local, its cell_transform describes result
placement, and its chunk_domain describes the grid cell. The global read
transform and the projection's chunk transform deliberately use different
coordinate frames.
An implementation must fill every cell of out in place, preserve the global
transform's exact values, order, and dtype, and return None. It must neither
replace nor retain out, which may be a strided writable view. Backend
exceptions propagate unchanged. Derived part views share their reader and may
be resolved concurrently, so a stateful reader owns its own synchronization.
Reader wrappers compose by intercepting this one operation and forwarding the same source, context, and output buffer to an inner reader:
class RecordingReader:
def __init__(self, inner):
self.inner = inner
self.calls = []
def read_into(self, source, context, out, /):
self.calls.append((source, context, out))
self.inner.read_into(source, context, out)
inner = RecordingReader(numpy_reader)
outer = RecordingReader(inner)
view = LazyArray.from_numpy(array).with_reader(outer)
values = view.result()
Both wrappers observe the same three objects, in outer-to-inner order. This delegation pattern supports policies such as logging and caching without library-defined wrapper primitives.
zarr_indexing.reader ¶
Backend reader protocol and built-in system-memory implementations.
__all__
module-attribute
¶
__all__ = [
"BasicReader",
"NumPyReader",
"ReadContext",
"Reader",
"UnitStepReader",
"basic_reader",
"numpy_reader",
"unit_step_reader",
]
BasicReader ¶
Reader for system-memory sources exposing basic integer/slice indexing.
Each transform is decomposed into the smallest enclosing positive-slice slab and a residual transform. The slab is read once with basic indexing, so fancy or negative-step selections may over-read, and the residual is then lowered through NumPy system-memory operations into the supplied buffer.
Slice results must permit conversion to NumPy system memory. Device arrays that reject implicit conversion require a custom reader responsible for transferring values into the supplied system-memory output buffer.
Examples:
>>> transform = IndexTransform.from_shape((6,))[1:5:2]
>>> source = np.arange(6)
>>> out = np.empty(transform.domain.shape, dtype=source.dtype)
>>> BasicReader().read_into(source, ReadContext(transform), out)
>>> out.tolist() == source[1:5:2].tolist()
True
Source code in src/zarr_indexing/reader.py
read_into ¶
read_into(
source: Any, context: ReadContext, out: Any
) -> None
Read one transform through a positive-slice slab and residual lowering.
Source code in src/zarr_indexing/reader.py
NumPyReader ¶
Reader optimized for NumPy system-memory arrays.
This is the reader selected by
LazyArray.from_numpy. It
applies the complete transform with NumPy operations and is applicable to
numpy.ndarray sources, including numpy.ma.MaskedArray.
Examples:
>>> transform = IndexTransform.from_shape((3, 4))[::2, 1:3]
>>> source = np.arange(12).reshape(3, 4)
>>> out = np.empty(transform.domain.shape, dtype=source.dtype)
>>> NumPyReader().read_into(source, ReadContext(transform), out)
>>> out.tolist()
[[1, 2], [9, 10]]
Source code in src/zarr_indexing/reader.py
read_into ¶
read_into(
source: Any, context: ReadContext, out: Any
) -> None
Read one transform through a narrowed slab into out.
Source code in src/zarr_indexing/reader.py
ReadContext
dataclass
¶
A source-global transform and optional projection for a partitioned read.
Examples:
>>> transform = IndexTransform.from_shape((6,))[1:5:2]
>>> context = ReadContext(transform)
>>> context.transform.domain.shape
(2,)
>>> context.projection is None
True
Source code in src/zarr_indexing/reader.py
projection
class-attribute
instance-attribute
¶
projection: ChunkProjection | None = None
The partition plan when this read is one part of a partitioned view, else None.
transform
instance-attribute
¶
transform: IndexTransform
Maps zero-origin output-buffer coordinates to global coordinates in the source.
__init__ ¶
__init__(
transform: IndexTransform,
projection: ChunkProjection | None = None,
) -> None
Reader ¶
Bases: Protocol
Backend adapter that fills supplied system-memory result buffers.
A reader may be shared by every view and part derived from one
LazyArray. Part reads may run
concurrently, so a stateful implementation must synchronize its own
mutable state. LazyArray deliberately adds no serialization.
Examples:
The protocol is not runtime_checkable; an object satisfies it by
exposing a conforming read_into, as basic_reader does:
>>> transform = IndexTransform.from_shape((6,))[1:5:2]
>>> source = np.arange(6)
>>> out = np.empty(transform.domain.shape, dtype=source.dtype)
>>> basic_reader.read_into(source, ReadContext(transform), out)
>>> out.tolist()
[1, 3]
Source code in src/zarr_indexing/reader.py
read_into ¶
read_into(
source: Any,
context: ReadContext,
out: ndarray[Any, Any],
) -> None
Fill out with the exact source values selected by context.
context.transform maps zero-origin coordinates in the output buffer
to global coordinates in source, and its domain shape equals
out.shape. context.projection, when present, is the corresponding
partition plan: its chunk_transform is chunk-local, its
cell_transform describes result placement, and its chunk_domain
describes the grid cell. Fill every cell in place, preserving the
transform's exact values, order, and dtype, then return None. Do not
replace or retain out; it may be a strided writable view rather than
an owning array.
Backend exceptions propagate unchanged. Because callers may resolve parts concurrently through the same reader object, stateful readers are responsible for synchronizing their own state.
Source code in src/zarr_indexing/reader.py
UnitStepReader ¶
Reader for sources whose basic indexing accepts only step-1 slices.
Each transform is decomposed into the smallest enclosing ascending
unit-step slab and a residual transform, so the source only ever receives
slice(start, stop, 1) on every axis — the one form an API without
general strided reads (an FFI binding, an HTTP range endpoint) supports.
BasicReader instead pushes strided and reversed slices down, which
reads less but asks more of the source.
The residual lowering applies strides, reversals, and gathers to the
in-memory block, so a strided selection over-reads its cover by the
stride factor. Partitioning the wrapping
LazyArray (with_parts) bounds
each cover by a part.
Slice results must permit conversion to NumPy system memory, exactly as
for BasicReader.
Examples:
The source below is only ever asked for step-1 slices — here the cover
slice(1, 4, 1) — and the stride is replayed against the block:
>>> transform = IndexTransform.from_shape((6,))[1:5:2]
>>> source = np.arange(6)
>>> out = np.empty(transform.domain.shape, dtype=source.dtype)
>>> UnitStepReader().read_into(source, ReadContext(transform), out)
>>> out.tolist()
[1, 3]
Source code in src/zarr_indexing/reader.py
read_into ¶
read_into(
source: Any, context: ReadContext, out: Any
) -> None
Read one transform through an ascending unit-step slab into out.