Skip to content

Public API for buffer objects #2876

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/2871.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add public :mod:`zarr.buffer` API for controlling how data is stored in memory.
4 changes: 2 additions & 2 deletions docs/user-guide/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ This is the current default configuration::
'string': {'name': 'vlen-utf8'}},
'write_empty_chunks': False},
'async': {'concurrency': 10, 'timeout': None},
'buffer': 'zarr.core.buffer.cpu.Buffer',
'buffer': 'zarr.buffer.cpu.Buffer',
'codec_pipeline': {'batch_size': 1,
'path': 'zarr.core.codec_pipeline.BatchedCodecPipeline'},
'codecs': {'blosc': 'zarr.codecs.blosc.BloscCodec',
Expand All @@ -87,5 +87,5 @@ This is the current default configuration::
'zstd': 'zarr.codecs.zstd.ZstdCodec'},
'default_zarr_format': 3,
'json_indent': 2,
'ndbuffer': 'zarr.core.buffer.cpu.NDBuffer',
'ndbuffer': 'zarr.buffer.cpu.NDBuffer',
'threading': {'max_workers': None}}
5 changes: 4 additions & 1 deletion docs/user-guide/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ Coming soon.
Custom array buffers
--------------------

Coming soon.
Zarr-python provides control over where and how arrays stored in memory through
:mod:`zarr.buffer`. Currently both CPU (the default) and GPU implementations are
provided (see :ref:`user-guide-gpu` for more). You can implement your own buffer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
provided (see :ref:`user-guide-gpu` for more). You can implement your own buffer
provided (see :ref:`user-guide-gpu` for more info). You can implement your own buffer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current version sounds OK.

classes by implementing the interface defined in :mod:`zarr.abc.buffer`.

Other extensions
----------------
Expand Down
9 changes: 9 additions & 0 deletions src/zarr/abc/buffer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from zarr.core.buffer.core import ArrayLike, Buffer, BufferPrototype, NDArrayLike, NDBuffer

__all__ = [
"ArrayLike",
"Buffer",
"BufferPrototype",
"NDArrayLike",
"NDBuffer",
]
12 changes: 12 additions & 0 deletions src/zarr/buffer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""
Implementations of the Zarr Buffer interface.

See Also
========
zarr.abc.buffer: Abstract base class for the Zarr Buffer interface.
"""

from zarr.buffer import cpu, gpu
from zarr.core.buffer import default_buffer_prototype

__all__ = ["cpu", "default_buffer_prototype", "gpu"]
15 changes: 15 additions & 0 deletions src/zarr/buffer/cpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from zarr.core.buffer.cpu import (
Buffer,
NDBuffer,
as_numpy_array_wrapper,
buffer_prototype,
numpy_buffer_prototype,
)

__all__ = [
"Buffer",
"NDBuffer",
"as_numpy_array_wrapper",
"buffer_prototype",
"numpy_buffer_prototype",
]
7 changes: 7 additions & 0 deletions src/zarr/buffer/gpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from zarr.core.buffer.gpu import Buffer, NDBuffer, buffer_prototype

__all__ = [
"Buffer",
"NDBuffer",
"buffer_prototype",
]
4 changes: 2 additions & 2 deletions src/zarr/core/buffer/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,5 @@ def numpy_buffer_prototype() -> core.BufferPrototype:
return core.BufferPrototype(buffer=Buffer, nd_buffer=NDBuffer)


register_buffer(Buffer)
register_ndbuffer(NDBuffer)
register_buffer(Buffer, qualname="zarr.buffer.cpu.Buffer")
register_ndbuffer(NDBuffer, qualname="zarr.buffer.cpu.NDBuffer")
4 changes: 2 additions & 2 deletions src/zarr/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ def enable_gpu(self) -> ConfigSet:
"vlen-utf8": "zarr.codecs.vlen_utf8.VLenUTF8Codec",
"vlen-bytes": "zarr.codecs.vlen_utf8.VLenBytesCodec",
},
"buffer": "zarr.core.buffer.cpu.Buffer",
"ndbuffer": "zarr.core.buffer.cpu.NDBuffer",
"buffer": "zarr.buffer.cpu.Buffer",
"ndbuffer": "zarr.buffer.cpu.NDBuffer",
}
],
)
Expand Down
2 changes: 1 addition & 1 deletion src/zarr/core/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,7 +1429,7 @@ async def create_hierarchy(
group already exists at path ``a``, then this function will leave the group at ``a`` as-is.

Yields
-------
------
tuple[str, AsyncArray | AsyncGroup].
"""
# check that all the nodes have the same zarr_format as Self
Expand Down
14 changes: 8 additions & 6 deletions src/zarr/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ def lazy_load(self) -> None:
self.register(e.load())
self.lazy_load_list.clear()

def register(self, cls: type[T]) -> None:
self[fully_qualified_name(cls)] = cls
def register(self, cls: type[T], qualname: str | None = None) -> None:
if qualname is None:
qualname = fully_qualified_name(cls)
self[qualname] = cls


__codec_registries: dict[str, Registry[Codec]] = defaultdict(Registry)
Expand Down Expand Up @@ -123,12 +125,12 @@ def register_pipeline(pipe_cls: type[CodecPipeline]) -> None:
__pipeline_registry.register(pipe_cls)


def register_ndbuffer(cls: type[NDBuffer]) -> None:
__ndbuffer_registry.register(cls)
def register_ndbuffer(cls: type[NDBuffer], qualname: str | None = None) -> None:
__ndbuffer_registry.register(cls, qualname)


def register_buffer(cls: type[Buffer]) -> None:
__buffer_registry.register(cls)
def register_buffer(cls: type[Buffer], qualname: str | None = None) -> None:
__buffer_registry.register(cls, qualname)


def get_codec_class(key: str, reload_config: bool = False) -> type[Codec]:
Expand Down
3 changes: 2 additions & 1 deletion tests/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import pytest

import zarr
from zarr.abc.buffer import ArrayLike, BufferPrototype, NDArrayLike
from zarr.buffer import cpu, gpu
from zarr.codecs.blosc import BloscCodec
from zarr.codecs.crc32c_ import Crc32cCodec
from zarr.codecs.gzip import GzipCodec
from zarr.codecs.transpose import TransposeCodec
from zarr.codecs.zstd import ZstdCodec
from zarr.core.buffer import ArrayLike, BufferPrototype, NDArrayLike, cpu, gpu
from zarr.storage import MemoryStore, StorePath
from zarr.testing.buffer import (
NDBufferUsingTestNDArrayLike,
Expand Down
10 changes: 2 additions & 8 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ def test_config_defaults_set() -> None:
"path": "zarr.core.codec_pipeline.BatchedCodecPipeline",
"batch_size": 1,
},
"buffer": "zarr.core.buffer.cpu.Buffer",
"ndbuffer": "zarr.core.buffer.cpu.NDBuffer",
"buffer": "zarr.buffer.cpu.Buffer",
"ndbuffer": "zarr.buffer.cpu.NDBuffer",
"codecs": {
"blosc": "zarr.codecs.blosc.BloscCodec",
"gzip": "zarr.codecs.gzip.GzipCodec",
Expand Down Expand Up @@ -223,9 +223,6 @@ class NewBloscCodec(BloscCodec):

@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"])
def test_config_ndbuffer_implementation(store: Store) -> None:
# has default value
assert fully_qualified_name(get_ndbuffer_class()) == config.defaults[0]["ndbuffer"]

# set custom ndbuffer with TestNDArrayLike implementation
register_ndbuffer(NDBufferUsingTestNDArrayLike)
with config.set({"ndbuffer": fully_qualified_name(NDBufferUsingTestNDArrayLike)}):
Expand All @@ -242,9 +239,6 @@ def test_config_ndbuffer_implementation(store: Store) -> None:


def test_config_buffer_implementation() -> None:
# has default value
assert fully_qualified_name(get_buffer_class()) == config.defaults[0]["buffer"]

arr = zeros(shape=(100), store=StoreExpectingTestBuffer())

# AssertionError of StoreExpectingTestBuffer when not using my buffer
Expand Down