2
2
3
3
import base64
4
4
import warnings
5
- from collections .abc import Iterable , Mapping , Sequence
5
+ from collections .abc import Iterable , Sequence
6
6
from enum import Enum
7
7
from functools import cached_property
8
8
from typing import TYPE_CHECKING , Any , TypedDict , cast
@@ -109,29 +109,6 @@ def shards(self) -> ChunkCoords | None:
109
109
return None
110
110
111
111
def to_buffer_dict (self , prototype : BufferPrototype ) -> dict [str , Buffer ]:
112
- def _serialize_fill_value (fv : Any ) -> JSON :
113
- if self .fill_value is None :
114
- pass
115
- elif self .dtype .kind in "SV" :
116
- # There's a relationship between self.dtype and self.fill_value
117
- # that mypy isn't aware of. The fact that we have S or V dtype here
118
- # means we should have a bytes-type fill_value.
119
- fv = base64 .standard_b64encode (cast (bytes , self .fill_value )).decode ("ascii" )
120
- elif isinstance (fv , np .datetime64 ):
121
- if np .isnat (fv ):
122
- fv = "NaT"
123
- else :
124
- fv = np .datetime_as_string (fv )
125
- elif isinstance (fv , numbers .Real ):
126
- float_fv = float (fv )
127
- if np .isnan (float_fv ):
128
- fv = "NaN"
129
- elif np .isinf (float_fv ):
130
- fv = "Infinity" if float_fv > 0 else "-Infinity"
131
- elif isinstance (fv , numbers .Complex ):
132
- fv = [_serialize_fill_value (fv .real ), _serialize_fill_value (fv .imag )]
133
- return cast (JSON , fv )
134
-
135
112
def _json_convert (
136
113
o : Any ,
137
114
) -> Any :
@@ -170,7 +147,7 @@ def _json_convert(
170
147
raise TypeError
171
148
172
149
zarray_dict = self .to_dict ()
173
- zarray_dict ["fill_value" ] = _serialize_fill_value (zarray_dict [ " fill_value" ] )
150
+ zarray_dict ["fill_value" ] = _serialize_fill_value (self . fill_value , self . dtype )
174
151
zattrs_dict = zarray_dict .pop ("attributes" , {})
175
152
json_indent = config .get ("json_indent" )
176
153
return {
@@ -185,7 +162,7 @@ def _json_convert(
185
162
}
186
163
187
164
@classmethod
188
- def from_dict (cls , data : dict [str , JSON ]) -> ArrayV2Metadata :
165
+ def from_dict (cls , data : dict [str , Any ]) -> ArrayV2Metadata :
189
166
# Make a copy to protect the original from modification.
190
167
_data = data .copy ()
191
168
# Check that the zarr_format attribute is correct.
@@ -213,7 +190,7 @@ def from_dict(cls, data: dict[str, JSON]) -> ArrayV2Metadata:
213
190
214
191
_data = {k : v for k , v in _data .items () if k in expected }
215
192
216
- return cls (** cast ( Mapping [ str , Any ], _data ) )
193
+ return cls (** _data )
217
194
218
195
def to_dict (self ) -> dict [str , JSON ]:
219
196
zarray_dict = super ().to_dict ()
@@ -315,7 +292,7 @@ def parse_metadata(data: ArrayV2Metadata) -> ArrayV2Metadata:
315
292
return data
316
293
317
294
318
- def parse_structured_fill_value (fill_value : Any , dtype : np .dtype [Any ]) -> Any :
295
+ def _parse_structured_fill_value (fill_value : Any , dtype : np .dtype [Any ]) -> Any :
319
296
"""Handle structured dtype/fill value pairs"""
320
297
try :
321
298
if isinstance (fill_value , list ):
@@ -354,7 +331,10 @@ def parse_fill_value(fill_value: Any, dtype: np.dtype[Any]) -> Any:
354
331
if fill_value is None or dtype .hasobject :
355
332
pass
356
333
elif dtype .fields is not None :
357
- fill_value = parse_structured_fill_value (fill_value , dtype )
334
+ # the dtype is structured (has multiple fields), so the fill_value might be a
335
+ # compound value (e.g., a tuple or dict) that needs field-wise processing.
336
+ # We use parse_structured_fill_value to correctly convert each component.
337
+ fill_value = _parse_structured_fill_value (fill_value , dtype )
358
338
elif not isinstance (fill_value , np .void ) and fill_value == 0 :
359
339
# this should be compatible across numpy versions for any array type, including
360
340
# structured arrays
@@ -369,16 +349,9 @@ def parse_fill_value(fill_value: Any, dtype: np.dtype[Any]) -> Any:
369
349
)
370
350
elif dtype .kind in "SV" and isinstance (fill_value , str ):
371
351
fill_value = base64 .standard_b64decode (fill_value )
372
- elif np .issubdtype (dtype , np .datetime64 ):
373
- if fill_value == "NaT" :
374
- fill_value = np .array ("NaT" , dtype = dtype )[()]
375
- else :
376
- fill_value = np .array (fill_value , dtype = dtype )[()]
377
352
elif dtype .kind == "c" and isinstance (fill_value , list ) and len (fill_value ) == 2 :
378
353
complex_val = complex (float (fill_value [0 ]), float (fill_value [1 ]))
379
354
fill_value = np .array (complex_val , dtype = dtype )[()]
380
- elif dtype .kind in "f" and fill_value in {"NaN" , "Infinity" , "-Infinity" }:
381
- fill_value = np .array (fill_value , dtype = dtype )[()]
382
355
else :
383
356
try :
384
357
if isinstance (fill_value , bytes ) and dtype .kind == "V" :
@@ -394,6 +367,39 @@ def parse_fill_value(fill_value: Any, dtype: np.dtype[Any]) -> Any:
394
367
return fill_value
395
368
396
369
370
+ def _serialize_fill_value (fill_value : Any , dtype : np .dtype [Any ]) -> JSON :
371
+ serialized : JSON
372
+
373
+ if fill_value is None :
374
+ serialized = None
375
+ elif dtype .kind in "SV" :
376
+ # There's a relationship between dtype and fill_value
377
+ # that mypy isn't aware of. The fact that we have S or V dtype here
378
+ # means we should have a bytes-type fill_value.
379
+ serialized = base64 .standard_b64encode (cast (bytes , fill_value )).decode ("ascii" )
380
+ elif isinstance (fill_value , np .datetime64 ):
381
+ serialized = np .datetime_as_string (fill_value )
382
+ elif isinstance (fill_value , numbers .Integral ):
383
+ serialized = int (fill_value )
384
+ elif isinstance (fill_value , numbers .Real ):
385
+ float_fv = float (fill_value )
386
+ if np .isnan (float_fv ):
387
+ serialized = "NaN"
388
+ elif np .isinf (float_fv ):
389
+ serialized = "Infinity" if float_fv > 0 else "-Infinity"
390
+ else :
391
+ serialized = float_fv
392
+ elif isinstance (fill_value , numbers .Complex ):
393
+ serialized = [
394
+ _serialize_fill_value (fill_value .real , dtype ),
395
+ _serialize_fill_value (fill_value .imag , dtype ),
396
+ ]
397
+ else :
398
+ serialized = fill_value
399
+
400
+ return serialized
401
+
402
+
397
403
def _default_fill_value (dtype : np .dtype [Any ]) -> Any :
398
404
"""
399
405
Get the default fill value for a type.
0 commit comments