Skip to content

Commit 0d6e48d

Browse files
committed
chore: internal cleanup
1 parent 0e01a21 commit 0d6e48d

File tree

5 files changed

+51
-79
lines changed

5 files changed

+51
-79
lines changed

src/zarr/codecs/cast_value.py

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ class ScalarMapJSON(TypedDict):
4444
OutOfRangeMode = Literal["clamp", "wrap"]
4545

4646

47-
class ScalarMap(TypedDict):
47+
class ScalarMap(TypedDict, total=False):
4848
"""
4949
The normalized, in-memory form of a scalar map.
5050
"""
5151

52-
encode: NotRequired[Mapping[str | float | int, str | float | int]]
53-
decode: NotRequired[Mapping[str | float | int, str | float | int]]
52+
encode: Mapping[str | float | int, str | float | int]
53+
decode: Mapping[str | float | int, str | float | int]
5454

5555

5656
def parse_scalar_map(obj: ScalarMapJSON | ScalarMap) -> ScalarMap:
@@ -85,33 +85,18 @@ def parse_scalar_map(obj: ScalarMapJSON | ScalarMap) -> ScalarMap:
8585
_HAS_RUST_BACKEND = False
8686

8787

88-
def _check_scalar_representable(
89-
value: str | float,
90-
dtype: np.dtype, # type: ignore[type-arg]
91-
direction: str,
92-
role: str,
88+
def _check_representable(
89+
value: JSON,
90+
zdtype: ZDType[TBaseDType, TBaseScalar],
91+
label: str,
9392
) -> None:
94-
"""Raise ``ValueError`` if *value* cannot be represented in *dtype*."""
95-
fval = float(value)
96-
is_integer_dtype = np.issubdtype(dtype, np.integer)
97-
if np.isnan(fval) and is_integer_dtype:
93+
"""Raise ``ValueError`` if *value* cannot be parsed by *zdtype*."""
94+
try:
95+
zdtype.from_json_scalar(value, zarr_format=3)
96+
except (TypeError, ValueError, OverflowError) as e:
9897
raise ValueError(
99-
f"scalar_map {direction} {role} {value!r} is NaN, "
100-
f"which is not representable in integer dtype {dtype}."
101-
)
102-
if is_integer_dtype:
103-
info = np.iinfo(dtype)
104-
ival = int(fval)
105-
if float(ival) != fval:
106-
raise ValueError(
107-
f"scalar_map {direction} {role} {value!r} is not an integer, "
108-
f"which is required for integer dtype {dtype}."
109-
)
110-
if ival < info.min or ival > info.max:
111-
raise ValueError(
112-
f"scalar_map {direction} {role} {value!r} is out of range "
113-
f"for dtype {dtype} [{info.min}, {info.max}]."
114-
)
98+
f"{label} {value!r} is not representable in dtype {zdtype.to_native_dtype()}."
99+
) from e
115100

116101

117102
# ---------------------------------------------------------------------------
@@ -215,28 +200,30 @@ def validate(
215200
raise ValueError("out_of_range='wrap' is only valid for integer target types.")
216201

217202
if self.scalar_map is not None:
218-
self._validate_scalar_map(source_native, target_native)
203+
self._validate_scalar_map(dtype, self.dtype)
219204

220205
def _validate_scalar_map(
221206
self,
222-
source_native: np.dtype, # type: ignore[type-arg]
223-
target_native: np.dtype, # type: ignore[type-arg]
207+
source_zdtype: ZDType[TBaseDType, TBaseScalar],
208+
target_zdtype: ZDType[TBaseDType, TBaseScalar],
224209
) -> None:
225210
"""Validate that scalar map entries are compatible with source/target dtypes."""
226211
assert self.scalar_map is not None
227212
# For encode: keys are source values, values are target values.
228213
# For decode: keys are target values, values are source values.
229-
direction_dtypes = {
230-
"encode": (source_native, target_native),
231-
"decode": (target_native, source_native),
214+
direction_dtypes: dict[
215+
str, tuple[ZDType[TBaseDType, TBaseScalar], ZDType[TBaseDType, TBaseScalar]]
216+
] = {
217+
"encode": (source_zdtype, target_zdtype),
218+
"decode": (target_zdtype, source_zdtype),
232219
}
233-
for direction, (key_dtype, val_dtype) in direction_dtypes.items():
220+
for direction, (key_zdtype, val_zdtype) in direction_dtypes.items():
234221
if direction not in self.scalar_map:
235222
continue
236223
sub_map = self.scalar_map[direction] # type: ignore[literal-required]
237224
for k, v in sub_map.items():
238-
_check_scalar_representable(k, key_dtype, direction, "key")
239-
_check_scalar_representable(v, val_dtype, direction, "value")
225+
_check_representable(k, key_zdtype, f"scalar_map {direction} key")
226+
_check_representable(v, val_zdtype, f"scalar_map {direction} value")
240227

241228
def _do_cast(
242229
self,

src/zarr/codecs/scale_offset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def validate(
8181
for name, value in [("offset", self.offset), ("scale", self.scale)]:
8282
try:
8383
dtype.from_json_scalar(value, zarr_format=3)
84-
except (TypeError, ValueError) as e:
84+
except (TypeError, ValueError, OverflowError) as e:
8585
raise ValueError(
8686
f"scale_offset {name} value {value!r} is not representable in dtype {native}."
8787
) from e

tests/test_codecs/conftest.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
5+
6+
@dataclass(frozen=True)
7+
class Expect[TIn, TOut]:
8+
"""Model an input and an expected output value for a test case."""
9+
10+
input: TIn
11+
expected: TOut
12+
13+
14+
@dataclass(frozen=True)
15+
class ExpectErr[TIn]:
16+
"""Model an input and an expected error message for a test case."""
17+
18+
input: TIn
19+
msg: str
20+
exception_cls: type[Exception]

tests/test_codecs/test_cast_value.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
43
from typing import Any
54

65
import numpy as np
76
import pytest
87

8+
from tests.test_codecs.conftest import Expect, ExpectErr
99
from zarr.codecs.cast_value import CastValue
1010

1111
try:
@@ -20,23 +20,6 @@
2020
)
2121

2222

23-
@dataclass(frozen=True)
24-
class Expect[TIn, TOut]:
25-
"""Model an input and an expected output value for a test case."""
26-
27-
input: TIn
28-
expected: TOut
29-
30-
31-
@dataclass(frozen=True)
32-
class ExpectErr[TIn]:
33-
"""Model an input and an expected error message for a test case."""
34-
35-
input: TIn
36-
msg: str
37-
exception_cls: type[Exception]
38-
39-
4023
# ---------------------------------------------------------------------------
4124
# Serialization
4225
# ---------------------------------------------------------------------------
@@ -336,7 +319,7 @@ def test_scalar_map_encode_decode_roundtrip() -> None:
336319
"target": "int8",
337320
"scalar_map": {"encode": [("NaN", 0)]},
338321
},
339-
msg="NaN.*not representable in integer dtype",
322+
msg="not representable in dtype int32",
340323
exception_cls=ValueError,
341324
),
342325
ExpectErr(
@@ -345,7 +328,7 @@ def test_scalar_map_encode_decode_roundtrip() -> None:
345328
"target": "float64",
346329
"scalar_map": {"decode": [(0, "NaN")]},
347330
},
348-
msg="NaN.*not representable in integer dtype",
331+
msg="not representable in dtype int32",
349332
exception_cls=ValueError,
350333
),
351334
ExpectErr(
@@ -354,7 +337,7 @@ def test_scalar_map_encode_decode_roundtrip() -> None:
354337
"target": "int8",
355338
"scalar_map": {"encode": [("NaN", 999)]},
356339
},
357-
msg="out of range",
340+
msg="not representable in dtype int8",
358341
exception_cls=ValueError,
359342
),
360343
ExpectErr(
@@ -363,7 +346,7 @@ def test_scalar_map_encode_decode_roundtrip() -> None:
363346
"target": "int8",
364347
"scalar_map": {"encode": [("NaN", 1.5)]},
365348
},
366-
msg="not an integer",
349+
msg="not representable in dtype int8",
367350
exception_cls=ValueError,
368351
),
369352
],

tests/test_codecs/test_scale_offset.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,13 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
43
from typing import Any
54

65
import numpy as np
76
import pytest
87

8+
from tests.test_codecs.conftest import Expect, ExpectErr
99
from zarr.codecs.scale_offset import ScaleOffset
1010

11-
12-
@dataclass(frozen=True)
13-
class Expect[TIn, TOut]:
14-
"""Model an input and an expected output value for a test case."""
15-
16-
input: TIn
17-
expected: TOut
18-
19-
20-
@dataclass(frozen=True)
21-
class ExpectErr[TIn]:
22-
"""Model an input and an expected error message for a test case."""
23-
24-
input: TIn
25-
msg: str
26-
exception_cls: type[Exception]
27-
28-
2911
# ---------------------------------------------------------------------------
3012
# Serialization
3113
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)