Skip to content

Commit 8c9aa99

Browse files
committed
Merge branch 'main' of github.com:zarr-developers/zarr-python into feat/v2-v3-codecs
2 parents ce28794 + e1990c0 commit 8c9aa99

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed

src/zarr/core/dtype/npy/common.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@
5858
IntishFloat = NewType("IntishFloat", float)
5959
"""A type for floats that represent integers, like 1.0 (but not 1.1)."""
6060

61+
IntishStr = NewType("IntishStr", str)
62+
"""A type for strings that represent integers, like "0" or "42"."""
63+
64+
FloatishStr = NewType("FloatishStr", str)
65+
"""A type for strings that represent floats, like "3.14" or "-2.5"."""
66+
6167
NumpyEndiannessStr = Literal[">", "<", "="]
6268
NUMPY_ENDIANNESS_STR: Final = ">", "<", "="
6369

@@ -488,6 +494,59 @@ def check_json_intish_float(data: JSON) -> TypeGuard[IntishFloat]:
488494
return isinstance(data, float) and data.is_integer()
489495

490496

497+
def check_json_intish_str(data: JSON) -> TypeGuard[IntishStr]:
498+
"""
499+
Check if a JSON value is a string that represents an integer, like "0", "42", or "-5".
500+
501+
Parameters
502+
----------
503+
data : JSON
504+
The JSON value to check.
505+
506+
Returns
507+
-------
508+
bool
509+
True if the data is a string representing an integer, False otherwise.
510+
"""
511+
if not isinstance(data, str):
512+
return False
513+
514+
try:
515+
int(data)
516+
except ValueError:
517+
return False
518+
else:
519+
return True
520+
521+
522+
def check_json_floatish_str(data: JSON) -> TypeGuard[FloatishStr]:
523+
"""
524+
Check if a JSON value is a string that represents a float, like "3.14", "-2.5", or "0.0".
525+
526+
Note: This function is intended to be used AFTER check_json_float_v2/v3, so it only
527+
handles regular string representations that those functions don't cover.
528+
529+
Parameters
530+
----------
531+
data : JSON
532+
The JSON value to check.
533+
534+
Returns
535+
-------
536+
bool
537+
True if the data is a string representing a regular float, False otherwise.
538+
"""
539+
if not isinstance(data, str):
540+
return False
541+
542+
try:
543+
float(data)
544+
except ValueError:
545+
return False
546+
else:
547+
return True
548+
549+
491550
def check_json_str(data: JSON) -> TypeGuard[str]:
492551
"""
493552
Check if a JSON value is a string.

src/zarr/core/dtype/npy/float.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
TFloatScalar_co,
2020
check_json_float_v2,
2121
check_json_float_v3,
22+
check_json_floatish_str,
2223
endianness_to_numpy_str,
2324
float_from_json_v2,
2425
float_from_json_v3,
@@ -270,13 +271,17 @@ def from_json_scalar(self, data: JSON, *, zarr_format: ZarrFormat) -> TFloatScal
270271
if zarr_format == 2:
271272
if check_json_float_v2(data):
272273
return self._cast_scalar_unchecked(float_from_json_v2(data))
274+
elif check_json_floatish_str(data):
275+
return self._cast_scalar_unchecked(float(data))
273276
else:
274277
raise TypeError(
275278
f"Invalid type: {data}. Expected a float or a special string encoding of a float."
276279
)
277280
elif zarr_format == 3:
278281
if check_json_float_v3(data):
279282
return self._cast_scalar_unchecked(float_from_json_v3(data))
283+
elif check_json_floatish_str(data):
284+
return self._cast_scalar_unchecked(float(data))
280285
else:
281286
raise TypeError(
282287
f"Invalid type: {data}. Expected a float or a special string encoding of a float."

src/zarr/core/dtype/npy/int.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from zarr.core.dtype.npy.common import (
2727
check_json_int,
2828
check_json_intish_float,
29+
check_json_intish_str,
2930
endianness_to_numpy_str,
3031
get_endianness_from_numpy_dtype,
3132
)
@@ -209,6 +210,10 @@ def from_json_scalar(self, data: JSON, *, zarr_format: ZarrFormat) -> TIntScalar
209210
return self._cast_scalar_unchecked(data)
210211
if check_json_intish_float(data):
211212
return self._cast_scalar_unchecked(int(data))
213+
214+
if check_json_intish_str(data):
215+
return self._cast_scalar_unchecked(int(data))
216+
212217
raise TypeError(f"Invalid type: {data}. Expected an integer.")
213218

214219
def to_json_scalar(self, data: object, *, zarr_format: ZarrFormat) -> int:

tests/test_dtype/test_npy/test_float.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,47 @@ class TestFloat64(_BaseTestFloat):
167167
("0x3ff0000000000000", 1.0),
168168
)
169169
item_size_params = (Float64(),)
170+
171+
172+
def test_check_json_floatish_str() -> None:
173+
"""Test the check_json_floatish_str function."""
174+
from zarr.core.dtype.npy.common import check_json_floatish_str
175+
176+
# Test valid string floats
177+
assert check_json_floatish_str("3.14")
178+
assert check_json_floatish_str("0.0")
179+
assert check_json_floatish_str("-2.5")
180+
assert check_json_floatish_str("1.0")
181+
182+
# Test invalid cases
183+
assert not check_json_floatish_str("not_a_number")
184+
assert not check_json_floatish_str("")
185+
assert not check_json_floatish_str(3.14) # actual float, not string
186+
assert not check_json_floatish_str(42) # int
187+
assert not check_json_floatish_str(None)
188+
189+
# Test that special cases still work via float() conversion
190+
# (these will be handled by existing functions first in practice)
191+
assert check_json_floatish_str("NaN")
192+
assert check_json_floatish_str("Infinity")
193+
assert check_json_floatish_str("-Infinity")
194+
195+
196+
def test_string_float_from_json_scalar() -> None:
197+
"""Test that string representations of floats can be parsed by from_json_scalar."""
198+
# Test with Float32
199+
dtype_instance = Float32()
200+
result = dtype_instance.from_json_scalar("3.14", zarr_format=3)
201+
assert abs(result - np.float32(3.14)) < 1e-6
202+
assert isinstance(result, np.float32)
203+
204+
# Test other cases
205+
result = dtype_instance.from_json_scalar("0.0", zarr_format=3)
206+
assert result == np.float32(0.0)
207+
208+
result = dtype_instance.from_json_scalar("-2.5", zarr_format=3)
209+
assert result == np.float32(-2.5)
210+
211+
# Test that it works for v2 format too
212+
result = dtype_instance.from_json_scalar("1.5", zarr_format=2)
213+
assert result == np.float32(1.5)

tests/test_dtype/test_npy/test_int.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,42 @@ class TestUInt64(BaseTestZDType):
281281
)
282282
invalid_scalar_params = ((UInt64(), {"set!"}), (UInt64(), ("tuple",)))
283283
item_size_params = (UInt64(),)
284+
285+
286+
def test_check_json_intish_str() -> None:
287+
"""Test the check_json_intish_str function."""
288+
from zarr.core.dtype.npy.common import check_json_intish_str
289+
290+
# Test valid string integers
291+
assert check_json_intish_str("0")
292+
assert check_json_intish_str("42")
293+
assert check_json_intish_str("-5")
294+
assert check_json_intish_str("123")
295+
296+
# Test invalid cases
297+
assert not check_json_intish_str("3.14")
298+
assert not check_json_intish_str("not_a_number")
299+
assert not check_json_intish_str("")
300+
assert not check_json_intish_str(42) # actual int, not string
301+
assert not check_json_intish_str(3.14) # float
302+
assert not check_json_intish_str(None)
303+
304+
305+
def test_string_integer_from_json_scalar() -> None:
306+
"""Test that string representations of integers can be parsed by from_json_scalar."""
307+
# Test the specific reproducer case
308+
dtype_instance = Int32()
309+
result = dtype_instance.from_json_scalar("0", zarr_format=3)
310+
assert result == np.int32(0)
311+
assert isinstance(result, np.int32)
312+
313+
# Test other cases
314+
result = dtype_instance.from_json_scalar("42", zarr_format=3)
315+
assert result == np.int32(42)
316+
317+
result = dtype_instance.from_json_scalar("-5", zarr_format=3)
318+
assert result == np.int32(-5)
319+
320+
# Test that it works for v2 format too
321+
result = dtype_instance.from_json_scalar("123", zarr_format=2)
322+
assert result == np.int32(123)

0 commit comments

Comments
 (0)