11from __future__ import annotations
22
33from abc import abstractmethod
4- from collections .abc import Iterable
4+ from collections .abc import Awaitable , Callable , Iterable
55from typing import TYPE_CHECKING , Generic , TypeVar
66
77from zarr .abc .metadata import Metadata
88from zarr .abc .store import ByteGetter , ByteSetter
99from zarr .buffer import Buffer , NDBuffer
10+ from zarr .common import concurrent_map
11+ from zarr .config import config
1012
1113if TYPE_CHECKING :
1214 from typing_extensions import Self
@@ -59,7 +61,7 @@ def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
5961 """
6062 return chunk_spec
6163
62- def evolve (self , array_spec : ArraySpec ) -> Self :
64+ def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Self :
6365 """Fills in codec configuration parameters that can be automatically
6466 inferred from the array metadata.
6567
@@ -83,7 +85,9 @@ def validate(self, array_metadata: ArrayMetadata) -> None:
8385 """
8486 ...
8587
86- @abstractmethod
88+ async def _decode_single (self , chunk_data : CodecOutput , chunk_spec : ArraySpec ) -> CodecInput :
89+ raise NotImplementedError
90+
8791 async def decode (
8892 self ,
8993 chunks_and_specs : Iterable [tuple [CodecOutput | None , ArraySpec ]],
@@ -100,9 +104,13 @@ async def decode(
100104 -------
101105 Iterable[CodecInput | None]
102106 """
103- ...
107+ return await batching_helper (self ._decode_single , chunks_and_specs )
108+
109+ async def _encode_single (
110+ self , chunk_data : CodecInput , chunk_spec : ArraySpec
111+ ) -> CodecOutput | None :
112+ raise NotImplementedError
104113
105- @abstractmethod
106114 async def encode (
107115 self ,
108116 chunks_and_specs : Iterable [tuple [CodecInput | None , ArraySpec ]],
@@ -119,7 +127,7 @@ async def encode(
119127 -------
120128 Iterable[CodecOutput | None]
121129 """
122- ...
130+ return await batching_helper ( self . _encode_single , chunks_and_specs )
123131
124132
125133class ArrayArrayCodec (_Codec [NDBuffer , NDBuffer ]):
@@ -146,7 +154,11 @@ class BytesBytesCodec(_Codec[Buffer, Buffer]):
146154class ArrayBytesCodecPartialDecodeMixin :
147155 """Mixin for array-to-bytes codecs that implement partial decoding."""
148156
149- @abstractmethod
157+ async def _decode_partial_single (
158+ self , byte_getter : ByteGetter , selection : SliceSelection , chunk_spec : ArraySpec
159+ ) -> NDBuffer | None :
160+ raise NotImplementedError
161+
150162 async def decode_partial (
151163 self ,
152164 batch_info : Iterable [tuple [ByteGetter , SliceSelection , ArraySpec ]],
@@ -167,13 +179,28 @@ async def decode_partial(
167179 -------
168180 Iterable[NDBuffer | None]
169181 """
170- ...
182+ return await concurrent_map (
183+ [
184+ (byte_getter , selection , chunk_spec )
185+ for byte_getter , selection , chunk_spec in batch_info
186+ ],
187+ self ._decode_partial_single ,
188+ config .get ("async.concurrency" ),
189+ )
171190
172191
173192class ArrayBytesCodecPartialEncodeMixin :
174193 """Mixin for array-to-bytes codecs that implement partial encoding."""
175194
176- @abstractmethod
195+ async def _encode_partial_single (
196+ self ,
197+ byte_setter : ByteSetter ,
198+ chunk_array : NDBuffer ,
199+ selection : SliceSelection ,
200+ chunk_spec : ArraySpec ,
201+ ) -> None :
202+ raise NotImplementedError
203+
177204 async def encode_partial (
178205 self ,
179206 batch_info : Iterable [tuple [ByteSetter , NDBuffer , SliceSelection , ArraySpec ]],
@@ -192,7 +219,14 @@ async def encode_partial(
192219 The ByteSetter is used to write the necessary bytes and fetch bytes for existing chunk data.
193220 The chunk spec contains information about the chunk.
194221 """
195- ...
222+ await concurrent_map (
223+ [
224+ (byte_setter , chunk_array , selection , chunk_spec )
225+ for byte_setter , chunk_array , selection , chunk_spec in batch_info
226+ ],
227+ self ._encode_partial_single ,
228+ config .get ("async.concurrency" ),
229+ )
196230
197231
198232class CodecPipeline (Metadata ):
@@ -203,7 +237,7 @@ class CodecPipeline(Metadata):
203237 and writes them to a store (via ByteSetter)."""
204238
205239 @abstractmethod
206- def evolve (self , array_spec : ArraySpec ) -> Self :
240+ def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Self :
207241 """Fills in codec configuration parameters that can be automatically
208242 inferred from the array metadata.
209243
@@ -347,3 +381,25 @@ async def write(
347381 value : NDBuffer
348382 """
349383 ...
384+
385+
386+ async def batching_helper (
387+ func : Callable [[CodecInput , ArraySpec ], Awaitable [CodecOutput | None ]],
388+ batch_info : Iterable [tuple [CodecInput | None , ArraySpec ]],
389+ ) -> list [CodecOutput | None ]:
390+ return await concurrent_map (
391+ [(chunk_array , chunk_spec ) for chunk_array , chunk_spec in batch_info ],
392+ noop_for_none (func ),
393+ config .get ("async.concurrency" ),
394+ )
395+
396+
397+ def noop_for_none (
398+ func : Callable [[CodecInput , ArraySpec ], Awaitable [CodecOutput | None ]],
399+ ) -> Callable [[CodecInput | None , ArraySpec ], Awaitable [CodecOutput | None ]]:
400+ async def wrap (chunk : CodecInput | None , chunk_spec : ArraySpec ) -> CodecOutput | None :
401+ if chunk is None :
402+ return None
403+ return await func (chunk , chunk_spec )
404+
405+ return wrap
0 commit comments