Skip to content

Commit 6fc05b7

Browse files
d-v-bjhamman
andauthored
parse_shapelike allows 0 (#1979)
* fill in shapelike tests, handle negative values correctly, allow 0 * Update tests/v3/test_common.py Co-authored-by: Joe Hamman <[email protected]> * move float test case to the appropriate test --------- Co-authored-by: Joe Hamman <[email protected]>
1 parent d4c25b2 commit 6fc05b7

File tree

2 files changed

+61
-33
lines changed

2 files changed

+61
-33
lines changed

src/zarr/common.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,21 @@ def parse_named_configuration(
135135

136136
def parse_shapelike(data: int | Iterable[int]) -> tuple[int, ...]:
137137
if isinstance(data, int):
138+
if data < 0:
139+
raise ValueError(f"Expected a non-negative integer. Got {data} instead")
138140
return (data,)
139-
if not isinstance(data, Iterable):
140-
raise TypeError(f"Expected an iterable. Got {data} instead.")
141-
data_tuple = tuple(data)
142-
if len(data_tuple) == 0:
143-
raise ValueError("Expected at least one element. Got 0.")
141+
try:
142+
data_tuple = tuple(data)
143+
except TypeError as e:
144+
msg = f"Expected an integer or an iterable of integers. Got {data} instead."
145+
raise TypeError(msg) from e
146+
144147
if not all(isinstance(v, int) for v in data_tuple):
145-
msg = f"Expected an iterable of integers. Got {type(data)} instead."
148+
msg = f"Expected an iterable of integers. Got {data} instead."
146149
raise TypeError(msg)
147-
if not all(lambda v: v > 0 for v in data_tuple):
148-
raise ValueError(f"All values must be greater than 0. Got {data}.")
150+
if not all(v > -1 for v in data_tuple):
151+
msg = f"Expected all values to be non-negative. Got {data} instead."
152+
raise ValueError(msg)
149153
return data_tuple
150154

151155

tests/v3/test_common.py

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,28 @@
1414

1515

1616
@pytest.mark.parametrize("data", [(0, 0, 0, 0), (1, 3, 4, 5, 6), (2, 4)])
17-
def test_product(data: tuple[int, ...]):
17+
def test_product(data: tuple[int, ...]) -> None:
1818
assert product(data) == np.prod(data)
1919

2020

2121
# todo: test
22-
def test_concurrent_map(): ...
22+
def test_concurrent_map() -> None: ...
2323

2424

2525
# todo: test
26-
def test_to_thread(): ...
26+
def test_to_thread() -> None: ...
2727

2828

2929
# todo: test
30-
def test_enum_names(): ...
30+
def test_enum_names() -> None: ...
3131

3232

3333
# todo: test
34-
def test_parse_enum(): ...
34+
def test_parse_enum() -> None: ...
3535

3636

3737
@pytest.mark.parametrize("data", [("foo", "bar"), (10, 11)])
38-
def test_parse_name_invalid(data: tuple[Any, Any]):
38+
def test_parse_name_invalid(data: tuple[Any, Any]) -> None:
3939
observed, expected = data
4040
if isinstance(observed, str):
4141
with pytest.raises(ValueError, match=f"Expected '{expected}'. Got {observed} instead."):
@@ -48,47 +48,71 @@ def test_parse_name_invalid(data: tuple[Any, Any]):
4848

4949

5050
@pytest.mark.parametrize("data", [("foo", "foo"), ("10", "10")])
51-
def test_parse_name_valid(data: tuple[Any, Any]):
51+
def test_parse_name_valid(data: tuple[Any, Any]) -> None:
5252
observed, expected = data
5353
assert parse_name(observed, expected) == observed
5454

5555

5656
@pytest.mark.parametrize("data", [0, 1, "hello", "f"])
57-
def test_parse_indexing_order_invalid(data):
57+
def test_parse_indexing_order_invalid(data: Any) -> None:
5858
with pytest.raises(ValueError, match="Expected one of"):
5959
parse_indexing_order(data)
6060

6161

6262
@pytest.mark.parametrize("data", ["C", "F"])
63-
def parse_indexing_order_valid(data: Literal["C", "F"]):
63+
def parse_indexing_order_valid(data: Literal["C", "F"]) -> None:
6464
assert parse_indexing_order(data) == data
6565

6666

67-
@pytest.mark.parametrize("data", [("0", 1, 2, 3), {"0": "0"}, []])
68-
def test_parse_shapelike_invalid(data: Any):
69-
if isinstance(data, Iterable):
70-
if len(data) == 0:
71-
with pytest.raises(ValueError, match="Expected at least one element."):
72-
parse_shapelike(data)
73-
else:
74-
with pytest.raises(TypeError, match="Expected an iterable of integers"):
75-
parse_shapelike(data)
76-
else:
77-
with pytest.raises(TypeError, match="Expected an iterable."):
78-
parse_shapelike(data)
67+
@pytest.mark.parametrize("data", [lambda v: v, slice(None)])
68+
def test_parse_shapelike_invalid_single_type(data: Any) -> None:
69+
"""
70+
Test that we get the expected error message when passing in a value that is not an integer
71+
or an iterable of integers.
72+
"""
73+
with pytest.raises(TypeError, match="Expected an integer or an iterable of integers."):
74+
parse_shapelike(data)
75+
76+
77+
def test_parse_shapelike_invalid_single_value() -> None:
78+
"""
79+
Test that we get the expected error message when passing in a negative integer.
80+
"""
81+
with pytest.raises(ValueError, match="Expected a non-negative integer."):
82+
parse_shapelike(-1)
83+
84+
85+
@pytest.mark.parametrize("data", ["shape", ("0", 1, 2, 3), {"0": "0"}, ((1, 2), (2, 2)), (4.0, 2)])
86+
def test_parse_shapelike_invalid_iterable_types(data: Any) -> None:
87+
"""
88+
Test that we get the expected error message when passing in an iterable containing
89+
non-integer elements
90+
"""
91+
with pytest.raises(TypeError, match="Expected an iterable of integers"):
92+
parse_shapelike(data)
93+
94+
95+
@pytest.mark.parametrize("data", [(1, 2, 3, -1), (-10,)])
96+
def test_parse_shapelike_invalid_iterable_values(data: Any) -> None:
97+
"""
98+
Test that we get the expected error message when passing in an iterable containing negative
99+
integers
100+
"""
101+
with pytest.raises(ValueError, match="Expected all values to be non-negative."):
102+
parse_shapelike(data)
79103

80104

81-
@pytest.mark.parametrize("data", [range(10), [0, 1, 2, 3], (3, 4, 5)])
82-
def test_parse_shapelike_valid(data: Iterable[Any]):
105+
@pytest.mark.parametrize("data", [range(10), [0, 1, 2, 3], (3, 4, 5), ()])
106+
def test_parse_shapelike_valid(data: Iterable[int]) -> None:
83107
assert parse_shapelike(data) == tuple(data)
84108

85109

86110
# todo: more dtypes
87111
@pytest.mark.parametrize("data", [("uint8", np.uint8), ("float64", np.float64)])
88-
def parse_dtype(data: tuple[str, np.dtype]):
112+
def parse_dtype(data: tuple[str, np.dtype]) -> None:
89113
unparsed, parsed = data
90114
assert parse_dtype(unparsed) == parsed
91115

92116

93117
# todo: figure out what it means to test this
94-
def test_parse_fill_value(): ...
118+
def test_parse_fill_value() -> None: ...

0 commit comments

Comments
 (0)