Skip to content

Commit

Permalink
Merge pull request #2360 from loichuder/visit-links
Browse files Browse the repository at this point in the history
Add methods to Group to visit links
  • Loading branch information
takluyver committed Apr 5, 2024
2 parents 0fb3d9e + b78f9dd commit 71af64d
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 0 deletions.
65 changes: 65 additions & 0 deletions h5py/_hl/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,9 @@ def move(self, source, dest):
def visit(self, func):
""" Recursively visit all names in this group and subgroups.
Note: visit ignores soft and external links. To visit those, use
visit_links.
You supply a callable (function, method or callable object); it
will be called exactly once for each link in this group and every
group below it. Your callable must conform to the signature:
Expand All @@ -639,6 +642,9 @@ def proxy(name):
def visititems(self, func):
""" Recursively visit names and objects in this group.
Note: visititems ignores soft and external links. To visit those, use
visititems_links.
You supply a callable (function, method or callable object); it
will be called exactly once for each link in this group and every
group below it. Your callable must conform to the signature:
Expand Down Expand Up @@ -667,6 +673,65 @@ def proxy(name):
return func(name, self[name])
return h5o.visit(self.id, proxy)

def visit_links(self, func):
""" Recursively visit all names in this group and subgroups.
Each link will be visited exactly once, regardless of its target.
You supply a callable (function, method or callable object); it
will be called exactly once for each link in this group and every
group below it. Your callable must conform to the signature:
func(<member name>) => <None or return value>
Returning None continues iteration, returning anything else stops
and immediately returns that value from the visit method. No
particular order of iteration within groups is guaranteed.
Example:
>>> # List the entire contents of the file
>>> f = File("foo.hdf5")
>>> list_of_names = []
>>> f.visit_links(list_of_names.append)
"""
with phil:
def proxy(name):
""" Call the function with the text name, not bytes """
return func(self._d(name))
return self.id.links.visit(proxy)

def visititems_links(self, func):
""" Recursively visit links in this group.
Each link will be visited exactly once, regardless of its target.
You supply a callable (function, method or callable object); it
will be called exactly once for each link in this group and every
group below it. Your callable must conform to the signature:
func(<member name>, <link>) => <None or return value>
Returning None continues iteration, returning anything else stops
and immediately returns that value from the visit method. No
particular order of iteration within groups is guaranteed.
Example:
# Get a list of all softlinks in the file
>>> mylist = []
>>> def func(name, link):
... if isinstance(link, SoftLink):
... mylist.append(name)
...
>>> f = File('foo.hdf5')
>>> f.visititems_links(func)
"""
with phil:
def proxy(name):
""" Use the text name of the object, not bytes """
name = self._d(name)
return func(name, self.get(name, getlink=True))
return self.id.links.visit(proxy)

@with_phil
def __repr__(self):
if not self:
Expand Down
42 changes: 42 additions & 0 deletions h5py/tests/test_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,48 @@ def test_bailout(self):
x = self.f.visititems(lambda x, y: (x,y))
self.assertEqual(x, (self.groups[0], self.f[self.groups[0]]))

class TestVisitLinks(TestCase):
"""
Feature: The .visit_links and .visititems_links methods allow iterative access to
links contained in the group and its subgroups.
"""

def setUp(self):
self.f = File(self.mktemp(), 'w')
self.groups = [
'grp1', 'grp1/grp11', 'grp1/grp12', 'grp2', 'grp2/grp21', 'grp2/grp21/grp211'
]
self.links = [
'linkto_grp1', 'grp1/linkto_grp11', 'grp1/linkto_grp12', 'linkto_grp2', 'grp2/linkto_grp21', 'grp2/grp21/linkto_grp211'
]
for g, l in zip(self.groups, self.links):
self.f.create_group(g)
self.f[l] = SoftLink(f'/{g}')

def tearDown(self):
self.f.close()

def test_visit_links(self):
""" All subgroups and links are visited """
l = []
self.f.visit_links(l.append)
self.assertSameElements(l, self.groups + self.links)

def test_visititems(self):
""" All links are visited """
l = []
comp = [(x, type(self.f.get(x, getlink=True))) for x in self.groups + self.links]
self.f.visititems_links(lambda x, y: l.append((x, type(y))))
self.assertSameElements(comp, l)

def test_bailout(self):
""" Returning a non-None value immediately aborts iteration """
x = self.f.visit_links(lambda x: x)
self.assertEqual(x, self.groups[0])
x = self.f.visititems_links(lambda x, y: (x,type(y)))
self.assertEqual(x, (self.groups[0], type(self.f.get(self.groups[0], getlink=True))))


class TestSoftLinks(BaseGroup):

"""
Expand Down
4 changes: 4 additions & 0 deletions news/visit-links.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
New features
------------

* Add methods `visit_links` and `visititems_links` that include links when visiting groups.

0 comments on commit 71af64d

Please sign in to comment.