Repository navigation
Expand file tree
/
Copy pathtest_json_parse.py
More file actions
96 lines (72 loc) · 3.58 KB
/
Copy pathtest_json_parse.py
File metadata and controls
96 lines (72 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
"""Tests for :mod:`zarr.core.json_parse`.
``convert`` delegates JSON type coercion to :func:`msgspec.convert` (translating
``msgspec.ValidationError`` into ``ValueError``). The final group covers the
``parse_storage_transformers`` generator-exhaustion regression.
"""
from __future__ import annotations
from typing import Literal
import pytest
from zarr.core.json_parse import convert, parse_field
from zarr.core.metadata.v3 import parse_storage_transformers
class TestConvert:
def test_literal(self) -> None:
assert convert(3, Literal[3]) == 3
assert convert("array", Literal["array", "group"]) == "array"
def test_literal_rejects_non_member(self) -> None:
with pytest.raises(ValueError, match="Expected instance of"):
convert(4, Literal[3])
with pytest.raises(ValueError, match="Expected instance of"):
convert("Q", Literal["C", "F"])
def test_sequence_coerced_to_tuple(self) -> None:
assert convert([1, 2, 3], tuple[int, ...]) == (1, 2, 3)
assert convert([1, 2], tuple[int, int]) == (1, 2)
def test_int(self) -> None:
assert convert(5, int) == 5
def test_bool_int_strictness(self) -> None:
# bool is an int subclass, but the two must not be interchangeable.
with pytest.raises(ValueError):
convert(True, int)
with pytest.raises(ValueError):
convert(1, bool)
# ... and True must not satisfy Literal[1].
with pytest.raises(ValueError):
convert(True, Literal[1])
class TestParseField:
def test_valid_passthrough(self) -> None:
assert parse_field(3, Literal[3], "zarr_format") == 3
def test_wraps_with_field_context(self) -> None:
with pytest.raises(ValueError, match="Failed to parse input for 'zarr_format'"):
parse_field(4, Literal[3], "zarr_format")
def test_custom_error_type_and_chaining(self) -> None:
class MyError(ValueError):
pass
with pytest.raises(MyError, match="Failed to parse input for 'node_type'") as exc_info:
parse_field(5, Literal["array"], "node_type", error=MyError)
# the generic type error is chained as the cause
assert isinstance(exc_info.value.__cause__, ValueError)
class TestStorageTransformersRegression:
"""`parse_storage_transformers` used to call `len(tuple(data))` and then
return `data` itself, exhausting a one-shot iterable and returning a value
typed as a tuple but not actually a tuple."""
def test_none(self) -> None:
assert parse_storage_transformers(None) == ()
def test_empty(self) -> None:
assert parse_storage_transformers([]) == ()
def test_list_returns_tuple(self) -> None:
result = parse_storage_transformers([{"a": 1}])
assert result == ({"a": 1},)
assert isinstance(result, tuple)
def test_generator_not_exhausted(self) -> None:
result = parse_storage_transformers(iter([{"a": 1}, {"b": 2}]))
assert result == ({"a": 1}, {"b": 2})
def test_non_iterable_rejected(self) -> None:
with pytest.raises(TypeError, match="Expected an iterable"):
parse_storage_transformers(5)
def test_dict_rejected(self) -> None:
"""A dict is iterable, but iterating it yields keys rather than
transformer objects, so it must be rejected rather than silently
swallowed."""
with pytest.raises(TypeError, match="Expected an iterable"):
parse_storage_transformers({})
with pytest.raises(TypeError, match="Expected an iterable"):
parse_storage_transformers({"name": "x"})