Skip to content

Commit 380e1d7

Browse files
committed
Support track order on group.get()
1 parent bcecf25 commit 380e1d7

File tree

4 files changed

+122
-24
lines changed

4 files changed

+122
-24
lines changed

h5pyd/_hl/attrs.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,16 @@ def __len__(self):
343343
def __iter__(self):
344344
""" Iterate over the names of attributes. """
345345
if self._objdb_attributes is not None:
346+
if self._parent._track_order:
347+
attrs = sorted(self._objdb_attributes.items(), key=lambda x: x[1]['created'])
348+
else:
349+
attrs = sorted(self._objdb_attributes.items())
350+
351+
ordered_attrs = {}
352+
for a in attrs:
353+
ordered_attrs[a[0]] = a[1]
346354

347-
for name in self._objdb_attributes:
355+
for name in ordered_attrs:
348356
yield name
349357

350358
else:
@@ -384,3 +392,33 @@ def __repr__(self):
384392
if not self._parent.id.id:
385393
return "<Attributes of closed HDF5 object>"
386394
return "<Attributes of HDF5 object at %s>" % id(self._parent.id)
395+
396+
def __reversed__(self):
397+
""" Iterate over the names of attributes in reverse order. """
398+
if self._objdb_attributes is not None:
399+
if self._parent._track_order:
400+
attrs = sorted(self._objdb_attributes.items(), key=lambda x: x[1]['created'])
401+
else:
402+
attrs = sorted(self._objdb_attributes.items())
403+
404+
ordered_attrs = {}
405+
for a in attrs:
406+
ordered_attrs[a[0]] = a[1]
407+
408+
for name in reversed(ordered_attrs):
409+
yield name
410+
411+
else:
412+
# make server request
413+
req = self._req_prefix
414+
# backup over the trailing slash in req
415+
req = req[:-1]
416+
rsp = self._parent.GET(req, params={"CreateOrder": "1" if self._parent._track_order else "0"})
417+
attributes = rsp['attributes']
418+
419+
attrlist = []
420+
for attr in attributes:
421+
attrlist.append(attr['name'])
422+
423+
for name in reversed(attrlist):
424+
yield name

h5pyd/_hl/dataset.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,7 +717,7 @@ def allocated_size(self):
717717
self._getVerboseInfo()
718718
return self._allocated_size
719719

720-
def __init__(self, bind):
720+
def __init__(self, bind, track_order=False):
721721
"""Create a new Dataset object by binding to a low-level DatasetID."""
722722

723723
if not isinstance(bind, DatasetID):
@@ -732,6 +732,7 @@ def __init__(self, bind):
732732
# make a numpy dtype out of the type json
733733
self._dtype = createDataType(self.id.type_json)
734734
self._item_size = getItemSize(self.id.type_json)
735+
self._track_order = track_order
735736

736737
self._shape = self.get_shape()
737738

h5pyd/_hl/group.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ def require_group(self, name):
550550
raise TypeError("Incompatible object (%s) already exists" % grp.__class__.__name__)
551551
return grp
552552

553-
def getObjByUuid(self, uuid, collection_type=None):
553+
def getObjByUuid(self, uuid, collection_type=None, track_order=False):
554554
""" Utility method to get an obj based on collection type and uuid """
555555
self.log.debug(f"getObjByUuid({uuid})")
556556
obj_json = None
@@ -585,10 +585,10 @@ def getObjByUuid(self, uuid, collection_type=None):
585585
# will need to get JSON from server
586586
req = f"/{collection_type}/{uuid}"
587587
# make server request
588-
obj_json = self.GET(req, params={"CreateOrder": "1" if self._track_order else "0"})
588+
obj_json = self.GET(req, params={"CreateOrder": "1" if track_order else "0"})
589589

590590
if collection_type == 'groups':
591-
tgt = Group(GroupID(self, obj_json))
591+
tgt = Group(GroupID(self, obj_json), track_order=track_order)
592592
elif collection_type == 'datatypes':
593593
tgt = Datatype(TypeID(self, obj_json))
594594
elif collection_type == 'datasets':
@@ -598,13 +598,13 @@ def getObjByUuid(self, uuid, collection_type=None):
598598
if "dims" in shape_json and len(shape_json["dims"]) == 1 and dtype_json["class"] == 'H5T_COMPOUND':
599599
tgt = Table(DatasetID(self, obj_json))
600600
else:
601-
tgt = Dataset(DatasetID(self, obj_json))
601+
tgt = Dataset(DatasetID(self, obj_json), track_order=track_order)
602602
else:
603603
raise IOError(f"Unexpected collection_type: {collection_type}")
604604

605605
return tgt
606606

607-
def __getitem__(self, name):
607+
def __getitem__(self, name, track_order=False):
608608
""" Open an object in the file """
609609
# convert bytes to str for PY3
610610
if isinstance(name, bytes):
@@ -617,11 +617,11 @@ def __getitem__(self, name):
617617
if tgt is not None:
618618
return tgt # ref'd object has not been deleted
619619
if isinstance(name.id, GroupID):
620-
tgt = self.getObjByUuid(name.id.uuid, collection_type="groups")
620+
tgt = self.getObjByUuid(name.id.uuid, collection_type="groups", track_order=track_order)
621621
elif isinstance(name.id, DatasetID):
622-
tgt = self.getObjByUuid(name.id.uuid, collection_type="datasets")
622+
tgt = self.getObjByUuid(name.id.uuid, collection_type="datasets", track_order=track_order)
623623
elif isinstance(name.id, TypeID):
624-
tgt = self.getObjByUuid(name.id.uuid, collection_type="datasets")
624+
tgt = self.getObjByUuid(name.id.uuid, collection_type="datasets", track_order=track_order)
625625
else:
626626
raise IOError("Unexpected Error - ObjectID type: " + name.__class__.__name__)
627627
return tgt
@@ -634,11 +634,11 @@ def __getitem__(self, name):
634634
link_class = link_json['class']
635635

636636
if link_class == 'H5L_TYPE_HARD':
637-
tgt = self.getObjByUuid(link_json['id'], collection_type=link_json['collection'])
637+
tgt = self.getObjByUuid(link_json['id'], collection_type=link_json['collection'], track_order=track_order)
638638
elif link_class == 'H5L_TYPE_SOFT':
639639
h5path = link_json['h5path']
640640
soft_parent_uuid, soft_json = self._get_link_json(h5path)
641-
tgt = self.getObjByUuid(soft_json['id'], collection_type=soft_json['collection'])
641+
tgt = self.getObjByUuid(soft_json['id'], collection_type=soft_json['collection'], track_order=track_order)
642642

643643
elif link_class == 'H5L_TYPE_EXTERNAL':
644644
# try to get a handle to the file and return the linked object...
@@ -654,7 +654,8 @@ def __getitem__(self, name):
654654
endpoint = self.id.http_conn.endpoint
655655
username = self.id.http_conn.username
656656
password = self.id.http_conn.password
657-
f = File(external_domain, endpoint=endpoint, username=username, password=password, mode='r')
657+
f = File(external_domain, endpoint=endpoint, username=username, password=password, mode='r',
658+
track_order=track_order)
658659
except IOError:
659660
# unable to find external link
660661
raise KeyError("Unable to open file: " + link_json['h5domain'])
@@ -678,7 +679,7 @@ def __getitem__(self, name):
678679
tgt._name = name
679680
return tgt
680681

681-
def get(self, name, default=None, getclass=False, getlink=False):
682+
def get(self, name, default=None, getclass=False, getlink=False, track_order=False):
682683
""" Retrieve an item or other information.
683684
684685
"name" given only:
@@ -702,18 +703,17 @@ def get(self, name, default=None, getclass=False, getlink=False):
702703
>>> if cls == SoftLink:
703704
... print '"foo" is a soft link!'
704705
"""
705-
706706
if not (getclass or getlink):
707707
try:
708-
return self[name]
708+
return self.__getitem__(name, track_order)
709709
except KeyError:
710710
return default
711711

712712
if name not in self:
713713
return default
714714

715715
elif getclass and not getlink:
716-
obj = self.__getitem__(name)
716+
obj = self.__getitem__(name, track_order)
717717
if obj is None:
718718
return None
719719
if obj.id.__class__ is GroupID:
@@ -891,7 +891,16 @@ def __iter__(self):
891891
for x in links:
892892
yield x['title']
893893
else:
894-
for name in links:
894+
if self._track_order:
895+
links = sorted(links.items(), key=lambda x: x[1]['created'])
896+
else:
897+
links = sorted(links.items())
898+
899+
ordered_links = {}
900+
for link in links:
901+
ordered_links[link[0]] = link[1]
902+
903+
for name in ordered_links:
895904
yield name
896905

897906
def __contains__(self, name):
@@ -1151,14 +1160,23 @@ def __reversed__(self):
11511160

11521161
# reset the link cache
11531162
self._link_db = {}
1154-
for link in reversed(links):
1163+
for link in links:
11551164
name = link["title"]
11561165
self._link_db[name] = link
11571166

11581167
for x in reversed(links):
11591168
yield x['title']
11601169
else:
1161-
for name in links:
1170+
if self._track_order:
1171+
links = sorted(links.items(), key=lambda x: x[1]['created'])
1172+
else:
1173+
links = sorted(links.items())
1174+
1175+
ordered_links = {}
1176+
for link in links:
1177+
ordered_links[link[0]] = link[1]
1178+
1179+
for name in reversed(ordered_links):
11621180
yield name
11631181

11641182

test/hl/test_group.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def test_create(self):
101101
self.assertTrue(False) # shouldn't get here'
102102
except RuntimeError:
103103
pass # expected
104+
except OSError:
105+
pass # also acceptable
104106

105107
del r['tmp']
106108
self.assertEqual(len(r), 4)
@@ -338,19 +340,27 @@ def test_no_track_order(self):
338340
self.assertEqual(list(reversed(g)), list(reversed(ref)))
339341

340342
def test_get_dataset_track_order(self):
343+
344+
# h5py does not support track_order on group.get()
345+
if config.get("use_h5py"):
346+
return
347+
341348
filename = self.getFileName("test_get_dataset_track_order")
342349
print(f"filename: {filename}")
343350
self.f = h5py.File(filename, 'w')
344351
g = self.f.create_group('order')
345-
# create dataset, close file, re-open and get dataset
352+
346353
dset = g.create_dataset('dset', (10,), dtype='i4')
347354
dset2 = g.create_dataset('dset2', (10,), dtype='i4')
348355

356+
self.populate_attrs(dset)
357+
self.populate_attrs(dset2)
358+
349359
self.f.close()
350-
f = h5py.File(filename, 'r')
351-
g = f['order']
352-
d = g.get('dset', track_order=True)
360+
self.f = h5py.File(filename, 'r')
361+
g = self.f['order']
353362

363+
d = g.get('dset', track_order=True)
354364
ref = [str(i) for i in range(100)]
355365
self.assertEqual(list(d.attrs), ref)
356366
self.assertEqual(list(reversed(d.attrs)), list(reversed(ref)))
@@ -360,6 +370,37 @@ def test_get_dataset_track_order(self):
360370
self.assertEqual(list(d2.attrs), ref)
361371
self.assertEqual(list(reversed(d2.attrs)), list(reversed(ref)))
362372

373+
def test_get_group_track_order(self):
374+
# h5py does not support track_order on group.get()
375+
if config.get("use_h5py"):
376+
return
377+
filename = self.getFileName("test_get_group_track_order")
378+
print(f"filename: {filename}")
379+
self.f = h5py.File(filename, 'w')
380+
g = self.f.create_group('order')
381+
382+
# create subgroup and populate it with links
383+
g.create_group('subgroup')
384+
self.populate(g['subgroup'])
385+
386+
self.f.close()
387+
self.f = h5py.File(filename, 'r')
388+
g = self.f['order']
389+
390+
subg = g.get('subgroup', track_order=True)
391+
ref = [str(i) for i in range(100)]
392+
self.assertEqual(list(subg), ref)
393+
self.assertEqual(list(reversed(subg)), list(reversed(ref)))
394+
395+
self.f.close()
396+
self.f = h5py.File(filename, 'r')
397+
g = self.f['order']
398+
subg2 = g.get('subgroup', track_order=False)
399+
ref = sorted([str(i) for i in range(100)])
400+
self.assertEqual(list(subg2), ref)
401+
self.assertEqual(list(reversed(subg2)), list(reversed(ref)))
402+
403+
363404
if __name__ == '__main__':
364405
loglevel = logging.ERROR
365406
logging.basicConfig(format='%(asctime)s %(message)s', level=loglevel)

0 commit comments

Comments
 (0)