forked from zarr-developers/pydantic-zarr
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcore.py
More file actions
167 lines (129 loc) · 5.08 KB
/
Copy pathcore.py
File metadata and controls
167 lines (129 loc) · 5.08 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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
from __future__ import annotations
from collections.abc import Mapping, Sequence
from typing import (
TYPE_CHECKING,
Any,
Literal,
TypeVar,
overload,
)
import numpy as np
import numpy.typing as npt
from pydantic import BaseModel
if TYPE_CHECKING:
import zarr
from zarr.storage._common import StoreLike
BaseAttributes = Mapping[str, object] | BaseModel
type IncEx = set[int] | set[str] | dict[int, Any] | dict[str, Any] | None
type AccessMode = Literal["w", "w+", "r", "a"]
T = TypeVar("T")
@overload
def tuplify_json(obj: Mapping) -> Mapping: ...
@overload
def tuplify_json(obj: list) -> tuple: ...
def tuplify_json(obj: object) -> object:
"""
Recursively converts lists within a Python object to tuples.
"""
if isinstance(obj, list):
return tuple(tuplify_json(elem) for elem in obj)
elif isinstance(obj, dict):
return {k: tuplify_json(v) for k, v in obj.items()}
else:
return obj
def parse_dtype_v2(value: npt.DTypeLike) -> str | list[tuple[Any, ...]]:
"""
Convert the input to a NumPy dtype and either return the ``str`` attribute of that
object or, if the dtype is a structured dtype, return the fields of that dtype as a list
of tuples.
Parameters
----------
value : npt.DTypeLike
A value that can be converted to a NumPy dtype.
Returns
-------
A Zarr V2-compatible encoding of the dtype.
References
----------
See the [Zarr V2 specification](https://zarr-specs.readthedocs.io/en/latest/v2/v2.0.html#data-type-encoding)
for more details on this encoding of data types.
"""
# Assume that a non-string sequence represents a the Zarr V2 JSON form of a structured dtype.
if isinstance(value, Sequence) and not isinstance(value, str):
return [tuple(v) for v in value]
else:
np_dtype = np.dtype(value)
if np_dtype.fields is not None:
# This is a structured dtype, which must be converted to a list of tuples. Note that
# this function recurses, because a structured dtype is parametrized by other dtypes.
return [(k, parse_dtype_v2(v[0])) for k, v in np_dtype.fields.items()]
else:
return np_dtype.str
def ensure_member_name(data: Any) -> str:
"""
If the input is a string, then ensure that it is a valid
name for a subnode in a zarr group
"""
if isinstance(data, str):
if "/" in data:
raise ValueError(
f'Strings containing "/" are invalid. Got {data}, which violates this rule.'
)
if data in ("", ".", ".."):
raise ValueError(f"The string {data} is not a valid member name.")
return data
raise TypeError(f"Expected a str, got {type(data)}.")
def ensure_key_no_path(data: Any) -> Any:
if isinstance(data, Mapping):
for key in data:
ensure_member_name(key)
return data
def model_like(a: BaseModel, b: BaseModel, exclude: IncEx = None, include: IncEx = None) -> bool:
"""
A similarity check for a pair pydantic.BaseModel, parametrized over included or excluded fields.
"""
a_dict = a.model_dump(exclude=exclude, include=include)
b_dict = b.model_dump(exclude=exclude, include=include)
return json_eq(a_dict, b_dict)
# TODO: expose contains_array and contains_group as public functions in zarr-python
# and replace these custom implementations
def maybe_node(
store: StoreLike, path: str, *, zarr_format: Literal[2, 3]
) -> zarr.Array | zarr.Group | None:
"""
Return the array or group found at the store / path, if an array or group exists there.
Otherwise return None.
"""
from zarr.core.sync import sync
from zarr.core.sync_group import get_node
from zarr.storage._common import make_store_path
# convert the storelike store argument to a Zarr store
spath = sync(make_store_path(store, path=path))
try:
return get_node(spath.store, spath.path, zarr_format=zarr_format)
except FileNotFoundError:
return None
def ensure_multiple(data: Sequence[T]) -> Sequence[T]:
"""
Ensure that there is at least one element in the sequence
"""
if len(data) < 1:
raise ValueError("Invalid length. Expected 1 or more, got 0.")
return data
def json_eq(a: object, b: object) -> bool:
"""
An equality check between python objects that recurses into dicts and sequences and ignores
the difference between tuples and lists. Otherwise, it's just regular equality. Useful
for comparing dicts that would become identical JSON, but where one has lists and the other
has tuples.
"""
# treat lists & tuples as the same "sequence" type
seq_types = (list, tuple)
# both are sequences → compare element-wise
if isinstance(a, seq_types) and isinstance(b, seq_types):
return len(a) == len(b) and all(json_eq(x, y) for x, y in zip(a, b, strict=False))
# recurse into mappings
if isinstance(a, Mapping) and isinstance(b, Mapping):
return a.keys() == b.keys() and all(json_eq(a[k], b[k]) for k in a)
# otherwise → regular equality
return a == b