Skip to content

Commit 724e6bf

Browse files
committed
added serialization methods for selection classes
1 parent f5c9f33 commit 724e6bf

2 files changed

Lines changed: 295 additions & 23 deletions

File tree

src/h5json/selections.py

Lines changed: 203 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ def select(obj, args, fields=None):
8484
if len(args) == 1:
8585

8686
arg = args[0]
87+
88+
if isinstance(arg, str):
89+
# convert seleection_str to tuple of slices/coordinates
90+
if arg == "[...]":
91+
args = (...,)
92+
else:
93+
args = _getSelectionList(obj_shape, arg)
94+
8795
if hasattr(arg, "shape"):
8896
arg_shape = arg.shape
8997
else:
@@ -105,7 +113,6 @@ def select(obj, args, fields=None):
105113
106114
return Selection(shape, spaceid=sid)
107115
"""
108-
109116
sel = SimpleSelection(obj_shape, args, fields=fields)
110117
return sel
111118

@@ -128,6 +135,161 @@ def _check_bool_args(s1, s2):
128135
raise ValueError("selections have incompatible shapes")
129136

130137

138+
def _getSelectElements(sel_str):
139+
"""helper method - return array of queries for each
140+
dimension"""
141+
if not isinstance(sel_str, str):
142+
raise TypeError("expected string arg")
143+
if len(sel_str) < 3:
144+
raise ValueError("selection string too short")
145+
if sel_str[0] != '[' or sel_str[-1] != ']':
146+
raise ValueError("unexpected selection string format")
147+
sel_str = sel_str[1:-1] # strip brackets
148+
149+
query_array = []
150+
dim_query = []
151+
coord_list = False
152+
for ch in sel_str:
153+
if ch.isspace():
154+
# ignore
155+
pass
156+
elif ch == ",":
157+
if coord_list:
158+
dim_query.append(ch)
159+
else:
160+
if len(dim_query) == 0:
161+
# empty dimension
162+
raise ValueError("invalid query")
163+
query_array.append("".join(dim_query))
164+
dim_query = [] # reset
165+
elif ch == "[":
166+
if coord_list:
167+
# can't have nested coordinates
168+
raise ValueError("invalid query")
169+
coord_list = True
170+
dim_query.append(ch)
171+
elif ch == "]":
172+
if not coord_list:
173+
# close bracket with no open
174+
raise ValueError("invalid query")
175+
dim_query.append(ch)
176+
coord_list = False
177+
elif ch == ":":
178+
if coord_list:
179+
# range not allowed in coord list
180+
raise ValueError("invalid query")
181+
dim_query.append(ch)
182+
else:
183+
dim_query.append(ch)
184+
if not dim_query:
185+
# empty dimension
186+
raise ValueError("invalid query")
187+
query_array.append("".join(dim_query))
188+
189+
return query_array
190+
191+
192+
def _getSelectionList(shape, sel_str):
193+
"""Return tuple of slices and/or coordinate list for the given selection"""
194+
select_list = []
195+
196+
if sel_str is None or len(sel_str) == 0:
197+
"""Return set of slices covering data space"""
198+
slices = []
199+
for extent in shape:
200+
s = slice(0, extent, 1)
201+
slices.append(s)
202+
return tuple(slices)
203+
204+
# convert selection to list by dimension
205+
elements = _getSelectElements(sel_str)
206+
rank = len(elements)
207+
if len(shape) != rank:
208+
raise ValueError("invalid rank for selection")
209+
for dim in range(rank):
210+
extent = shape[dim]
211+
element = elements[dim]
212+
is_list = isinstance(element, list)
213+
is_str = isinstance(element, str)
214+
if is_list or (is_str and element.startswith("[")):
215+
# list of coordinates
216+
if is_str:
217+
fields = element[1:-1].split(",")
218+
else:
219+
fields = element
220+
coords = []
221+
for field in fields:
222+
if isinstance(field, str) and not field:
223+
continue
224+
try:
225+
coord = int(field)
226+
except ValueError:
227+
raise ValueError(f"Invalid coordinate for dim {dim}")
228+
if coord < 0 or coord >= extent:
229+
msg = f"out of range coordinate for dim {dim}, {coord} "
230+
msg += f"not in range: 0-{extent - 1}"
231+
raise ValueError(msg)
232+
coords.append(coord)
233+
select_list.append(coords)
234+
elif element == ":":
235+
s = slice(0, extent, 1)
236+
select_list.append(s)
237+
elif is_str and element.find(":") >= 0:
238+
fields = element.split(":")
239+
if len(fields) not in (2, 3):
240+
raise ValueError(f"Invalid selection format for dim {dim}")
241+
if len(fields[0]) == 0:
242+
start = 0
243+
else:
244+
try:
245+
start = int(fields[0])
246+
except ValueError:
247+
raise ValueError(f"Invalid selection - start value for dim {dim}")
248+
if start < 0 or start >= extent:
249+
msg = f"Invalid selection - start value out of range for dim {dim}"
250+
raise ValueError(msg)
251+
if len(fields[1]) == 0:
252+
stop = extent
253+
else:
254+
try:
255+
stop = int(fields[1])
256+
except ValueError:
257+
raise ValueError(f"Invalid selection - stop value for dim {dim}")
258+
if stop < 0 or stop > extent or stop <= start:
259+
msg = f"Invalid selection - stop value out of range for dim {dim}"
260+
raise ValueError(msg)
261+
if len(fields) == 3:
262+
# get step value
263+
if len(fields[2]) == 0:
264+
step = 1
265+
else:
266+
try:
267+
step = int(fields[2])
268+
except ValueError:
269+
msg = f"Invalid selection - step value for dim {dim}"
270+
raise ValueError(msg)
271+
if step <= 0:
272+
msg = f"Invalid selection - step value out of range for dim {dim}"
273+
raise ValueError(msg)
274+
else:
275+
step = 1
276+
s = slice(start, stop, step)
277+
select_list.append(s)
278+
else:
279+
# expect single coordinate value
280+
try:
281+
index = int(element)
282+
except ValueError:
283+
raise ValueError(f"Invalid selection - index value for dim {dim}")
284+
if index < 0 or index >= extent:
285+
msg = f"Invalid selection - index value out of range for dim {dim}"
286+
raise ValueError(msg)
287+
s = slice(index, index + 1, 1)
288+
select_list.append(s)
289+
# end dimension loop
290+
return tuple(select_list)
291+
292+
131293
def _points_to_paired(shape, points):
132294
"""Convert a list of point coordinates or a boolean array into the
133295
per-dimension tuple expected by SimpleSelection's fancy path.
@@ -794,6 +956,17 @@ def broadcast(self, target_shape):
794956
def __getitem__(self, args):
795957
raise NotImplementedError("This class does not support indexing")
796958

959+
def __eq__(self, other):
960+
if not isinstance(other, Selection):
961+
return NotImplemented
962+
return all((
963+
type(self) is type(other),
964+
self.shape == other.shape,
965+
self.select_type == other.select_type,
966+
self.fields == other.fields,
967+
self.mshape == other.mshape,
968+
))
969+
797970
def __repr__(self):
798971
return f"Selection(shape:{self._shape})"
799972

@@ -964,8 +1137,9 @@ def getSelectNpoints(self):
9641137
npoints *= m
9651138
return npoints
9661139

967-
def getQueryParam(self):
968-
""" Get select param for use with HDF Rest API"""
1140+
@property
1141+
def query_string(self):
1142+
""" The value of the 'select' query parameter for this selection, for use with the HDF REST API """
9691143
rank = len(self._shape)
9701144
if rank == 0:
9711145
return None
@@ -1047,6 +1221,16 @@ def broadcast(self, target_shape):
10471221
sel = [tuple([sum(x) for x in zip(offset, start)]), tshape, step, scalar]
10481222
yield sel
10491223

1224+
def __eq__(self, other):
1225+
if not isinstance(other, SimpleSelection):
1226+
return NotImplemented
1227+
return all((
1228+
self.shape == other.shape,
1229+
self.select_type == other.select_type,
1230+
self.fields == other.fields,
1231+
self.slices == other.slices,
1232+
))
1233+
10501234
def __repr__(self):
10511235
if self.fields:
10521236
fields = ", fields: " + str(self.fields)
@@ -1271,3 +1455,19 @@ def __init__(self, shape, *args, **kwds):
12711455
self._select_type = H5S_SEL_ALL
12721456
else:
12731457
raise ValueError("Illegal slicing argument for scalar dataspace")
1458+
1459+
def __eq__(self, other):
1460+
if not isinstance(other, ScalarSelection):
1461+
return NotImplemented
1462+
# mshape is not compared here: () vs (Ellipsis,) construction args produce
1463+
# different mshape (None vs ()) despite selecting the same scalar element,
1464+
# and select_type is H5S_SEL_ALL in both cases anyway.
1465+
return all((
1466+
self.shape == other.shape,
1467+
self.select_type == other.select_type,
1468+
self.fields == other.fields,
1469+
))
1470+
1471+
@property
1472+
def query_string(self):
1473+
return None

0 commit comments

Comments
 (0)