Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
8 changes: 8 additions & 0 deletions changes/2871.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Added public API for Buffer ABCs and implementations.

Use :mod:`zarr.buffer` to access buffer implementations, and
:mod:`zarr.abc.buffer` for the interface to implement new buffer types.

Users previously importing buffer from ``zarr.core.buffer`` should update their
imports to use :mod:`zarr.buffer`. As a reminder, all of ``zarr.core`` is
considered a private API that's not covered by zarr-python's versioning policy.
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
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",
]
9 changes: 7 additions & 2 deletions src/zarr/core/buffer/cpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,10 @@ 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")


# backwards compatibility
register_buffer(Buffer, qualname="zarr.core.buffer.cpu.Buffer")
register_ndbuffer(NDBuffer, qualname="zarr.core.buffer.cpu.NDBuffer")
8 changes: 6 additions & 2 deletions src/zarr/core/buffer/gpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,9 @@ def __setitem__(self, key: Any, value: Any) -> None:

buffer_prototype = BufferPrototype(buffer=Buffer, nd_buffer=NDBuffer)

register_buffer(Buffer)
register_ndbuffer(NDBuffer)
register_buffer(Buffer, qualname="zarr.buffer.gpu.Buffer")
register_ndbuffer(NDBuffer, qualname="zarr.buffer.gpu.NDBuffer")

# backwards compatibility
register_buffer(Buffer, qualname="zarr.core.buffer.gpu.Buffer")
register_ndbuffer(NDBuffer, qualname="zarr.core.buffer.gpu.NDBuffer")
6 changes: 3 additions & 3 deletions src/zarr/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def enable_gpu(self) -> ConfigSet:
Configure Zarr to use GPUs where possible.
"""
return self.set(
{"buffer": "zarr.core.buffer.gpu.Buffer", "ndbuffer": "zarr.core.buffer.gpu.NDBuffer"}
{"buffer": "zarr.buffer.gpu.Buffer", "ndbuffer": "zarr.buffer.gpu.NDBuffer"}
)


Expand Down 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 @@ -1451,7 +1451,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
30 changes: 24 additions & 6 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,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 @@ -243,7 +240,7 @@ 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"]
assert config.defaults[0]["buffer"] == "zarr.buffer.cpu.Buffer"

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

Expand Down Expand Up @@ -278,6 +275,27 @@ def test_config_buffer_implementation() -> None:
assert np.array_equal(arr_Crc32c[:], data2d)


def test_config_buffer_backwards_compatibility() -> None:
# This should warn once zarr.core is private
# https://github.com/zarr-developers/zarr-python/issues/2621
with zarr.config.set(
{"buffer": "zarr.core.buffer.cpu.Buffer", "ndbuffer": "zarr.core.buffer.cpu.NDBuffer"}
):
get_buffer_class()
get_ndbuffer_class()


@pytest.mark.gpu
def test_config_buffer_backwards_compatibility_gpu() -> None:
# This should warn once zarr.core is private
# https://github.com/zarr-developers/zarr-python/issues/2621
with zarr.config.set(
{"buffer": "zarr.core.buffer.gpu.Buffer", "ndbuffer": "zarr.core.buffer.gpu.NDBuffer"}
):
get_buffer_class()
get_ndbuffer_class()


@pytest.mark.filterwarnings("error")
def test_warning_on_missing_codec_config() -> None:
class NewCodec(BytesCodec):
Expand Down