Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions changes/3448.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Setting ``fill_value`` to a float like ``0.0`` when the data type of the array is an integer is a common
mistake. This change lets Zarr Python read arrays with this erroneous metadata, although Zarr Python
will not create such arrays.
21 changes: 21 additions & 0 deletions src/zarr/core/dtype/npy/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Any,
Final,
Literal,
NewType,
SupportsComplex,
SupportsFloat,
SupportsIndex,
Expand Down Expand Up @@ -54,6 +55,9 @@
"generic",
)

IntishFloat = NewType("IntishFloat", float)
"""A type for floats that represent integers, like 1.0 (but not 1.1)."""

NumpyEndiannessStr = Literal[">", "<", "="]
NUMPY_ENDIANNESS_STR: Final = ">", "<", "="

Expand Down Expand Up @@ -467,6 +471,23 @@ def check_json_int(data: JSON) -> TypeGuard[int]:
return bool(isinstance(data, int))


def check_json_intish_float(data: JSON) -> TypeGuard[IntishFloat]:
"""
Check if a JSON value is an "intish float", i.e. a float that represents an integer, like 0.0.

Parameters
----------
data : JSON
The JSON value to check.

Returns
-------
Bool
True if the data is an intish float, False otherwise.
"""
return isinstance(data, float) and data.is_integer()


def check_json_str(data: JSON) -> TypeGuard[str]:
"""
Check if a JSON value is a string.
Expand Down
3 changes: 3 additions & 0 deletions src/zarr/core/dtype/npy/int.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
)
from zarr.core.dtype.npy.common import (
check_json_int,
check_json_intish_float,
endianness_to_numpy_str,
get_endianness_from_numpy_dtype,
)
Expand Down Expand Up @@ -206,6 +207,8 @@ def from_json_scalar(self, data: JSON, *, zarr_format: ZarrFormat) -> TIntScalar
"""
if check_json_int(data):
return self._cast_scalar_unchecked(data)
if check_json_intish_float(data):
return self._cast_scalar_unchecked(int(data))
raise TypeError(f"Invalid type: {data}. Expected an integer.")

def to_json_scalar(self, data: object, *, zarr_format: ZarrFormat) -> int:
Expand Down
8 changes: 8 additions & 0 deletions tests/test_dtype/test_npy/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
check_json_float_v2,
check_json_float_v3,
check_json_int,
check_json_intish_float,
check_json_str,
complex_float_to_json_v2,
complex_float_to_json_v3,
Expand Down Expand Up @@ -320,6 +321,13 @@ def test_check_json_int() -> None:
assert not check_json_int(1.0)


def test_check_json_intish_float() -> None:
assert check_json_intish_float(0.0)
assert check_json_intish_float(1.0)
assert not check_json_intish_float("0")
assert not check_json_intish_float(1.1)


def test_check_json_str() -> None:
assert check_json_str("0")
assert not check_json_str(1.0)
Expand Down
16 changes: 8 additions & 8 deletions tests/test_dtype/test_npy/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class TestInt8(BaseTestZDType):
{"name": "int8", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((Int8(), 1), (Int8(), -1))
scalar_v2_params = ((Int8(), 1), (Int8(), -1), (Int8(), 1.0))
scalar_v3_params = ((Int8(), 1), (Int8(), -1))
cast_value_params = (
(Int8(), 1, np.int8(1)),
Expand Down Expand Up @@ -63,7 +63,7 @@ class TestInt16(BaseTestZDType):
{"name": "int16", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((Int16(), 1), (Int16(), -1))
scalar_v2_params = ((Int16(), 1), (Int16(), -1), (Int16(), 1.0))
scalar_v3_params = ((Int16(), 1), (Int16(), -1))
cast_value_params = (
(Int16(), 1, np.int16(1)),
Expand Down Expand Up @@ -101,7 +101,7 @@ class TestInt32(BaseTestZDType):
{"name": "int32", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((Int32(), 1), (Int32(), -1))
scalar_v2_params = ((Int32(), 1), (Int32(), -1), (Int32(), 1.0))
scalar_v3_params = ((Int32(), 1), (Int32(), -1))
cast_value_params = (
(Int32(), 1, np.int32(1)),
Expand Down Expand Up @@ -136,7 +136,7 @@ class TestInt64(BaseTestZDType):
{"name": "int64", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((Int64(), 1), (Int64(), -1))
scalar_v2_params = ((Int64(), 1), (Int64(), -1), (Int64(), 1.0))
scalar_v3_params = ((Int64(), 1), (Int64(), -1))
cast_value_params = (
(Int64(), 1, np.int64(1)),
Expand Down Expand Up @@ -168,7 +168,7 @@ class TestUInt8(BaseTestZDType):
{"name": "uint8", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((UInt8(), 1), (UInt8(), 0))
scalar_v2_params = ((UInt8(), 1), (UInt8(), 0), (UInt8(), 1.0))
scalar_v3_params = ((UInt8(), 1), (UInt8(), 0))
cast_value_params = (
(UInt8(), 1, np.uint8(1)),
Expand Down Expand Up @@ -203,7 +203,7 @@ class TestUInt16(BaseTestZDType):
{"name": "uint16", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((UInt16(), 1), (UInt16(), 0))
scalar_v2_params = ((UInt16(), 1), (UInt16(), 0), (UInt16(), 1.0))
scalar_v3_params = ((UInt16(), 1), (UInt16(), 0))
cast_value_params = (
(UInt16(), 1, np.uint16(1)),
Expand Down Expand Up @@ -238,7 +238,7 @@ class TestUInt32(BaseTestZDType):
{"name": "uint32", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((UInt32(), 1), (UInt32(), 0))
scalar_v2_params = ((UInt32(), 1), (UInt32(), 0), (UInt32(), 1.0))
scalar_v3_params = ((UInt32(), 1), (UInt32(), 0))
cast_value_params = (
(UInt32(), 1, np.uint32(1)),
Expand Down Expand Up @@ -273,7 +273,7 @@ class TestUInt64(BaseTestZDType):
{"name": "uint64", "configuration": {"endianness": "little"}},
)

scalar_v2_params = ((UInt64(), 1), (UInt64(), 0))
scalar_v2_params = ((UInt64(), 1), (UInt64(), 0), (UInt64(), 1.0))
scalar_v3_params = ((UInt64(), 1), (UInt64(), 0))
cast_value_params = (
(UInt64(), 1, np.uint64(1)),
Expand Down
Loading