1
1
from __future__ import annotations
2
2
3
3
from abc import abstractmethod
4
- from collections .abc import Iterable
4
+ from collections .abc import Awaitable , Callable , Iterable
5
5
from typing import TYPE_CHECKING , Generic , TypeVar
6
6
7
7
from zarr .abc .metadata import Metadata
8
8
from zarr .abc .store import ByteGetter , ByteSetter
9
9
from zarr .buffer import Buffer , NDBuffer
10
+ from zarr .common import concurrent_map
11
+ from zarr .config import config
10
12
11
13
if TYPE_CHECKING :
12
14
from typing_extensions import Self
@@ -59,7 +61,7 @@ def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
59
61
"""
60
62
return chunk_spec
61
63
62
- def evolve (self , array_spec : ArraySpec ) -> Self :
64
+ def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Self :
63
65
"""Fills in codec configuration parameters that can be automatically
64
66
inferred from the array metadata.
65
67
@@ -83,7 +85,9 @@ def validate(self, array_metadata: ArrayMetadata) -> None:
83
85
"""
84
86
...
85
87
86
- @abstractmethod
88
+ async def _decode_single (self , chunk_data : CodecOutput , chunk_spec : ArraySpec ) -> CodecInput :
89
+ raise NotImplementedError
90
+
87
91
async def decode (
88
92
self ,
89
93
chunks_and_specs : Iterable [tuple [CodecOutput | None , ArraySpec ]],
@@ -100,9 +104,13 @@ async def decode(
100
104
-------
101
105
Iterable[CodecInput | None]
102
106
"""
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
104
113
105
- @abstractmethod
106
114
async def encode (
107
115
self ,
108
116
chunks_and_specs : Iterable [tuple [CodecInput | None , ArraySpec ]],
@@ -119,7 +127,7 @@ async def encode(
119
127
-------
120
128
Iterable[CodecOutput | None]
121
129
"""
122
- ...
130
+ return await batching_helper ( self . _encode_single , chunks_and_specs )
123
131
124
132
125
133
class ArrayArrayCodec (_Codec [NDBuffer , NDBuffer ]):
@@ -146,7 +154,11 @@ class BytesBytesCodec(_Codec[Buffer, Buffer]):
146
154
class ArrayBytesCodecPartialDecodeMixin :
147
155
"""Mixin for array-to-bytes codecs that implement partial decoding."""
148
156
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
+
150
162
async def decode_partial (
151
163
self ,
152
164
batch_info : Iterable [tuple [ByteGetter , SliceSelection , ArraySpec ]],
@@ -167,13 +179,28 @@ async def decode_partial(
167
179
-------
168
180
Iterable[NDBuffer | None]
169
181
"""
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
+ )
171
190
172
191
173
192
class ArrayBytesCodecPartialEncodeMixin :
174
193
"""Mixin for array-to-bytes codecs that implement partial encoding."""
175
194
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
+
177
204
async def encode_partial (
178
205
self ,
179
206
batch_info : Iterable [tuple [ByteSetter , NDBuffer , SliceSelection , ArraySpec ]],
@@ -192,7 +219,14 @@ async def encode_partial(
192
219
The ByteSetter is used to write the necessary bytes and fetch bytes for existing chunk data.
193
220
The chunk spec contains information about the chunk.
194
221
"""
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
+ )
196
230
197
231
198
232
class CodecPipeline (Metadata ):
@@ -203,7 +237,7 @@ class CodecPipeline(Metadata):
203
237
and writes them to a store (via ByteSetter)."""
204
238
205
239
@abstractmethod
206
- def evolve (self , array_spec : ArraySpec ) -> Self :
240
+ def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Self :
207
241
"""Fills in codec configuration parameters that can be automatically
208
242
inferred from the array metadata.
209
243
@@ -347,3 +381,25 @@ async def write(
347
381
value : NDBuffer
348
382
"""
349
383
...
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