minimal implementation: allow to display the data tree
::
python h5fs.py path/to/hdf5file.hdf5 /path/to/mountpoint
tree /path/to/mountpoint
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/h5fs.py Tue Apr 03 20:34:00 2012 +0200
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2012 Alain Leufroy <alain.leufroy@logilab.fr>
+#
+# This program can be distributed under the terms of the GNU LGPL.
+# See the file COPYING.
+#
+
+'''
+HDF% filesystem implementation.
+'''
+
+import os
+import os.path as osp
+import stat
+import errno
+
+import h5py
+
+import fuse
+fuse.fuse_python_api = (0, 2)
+
+__version__ = (0, 0, 1)
+
+
+class H5IOError(IOError):
+ """Raised when a bad hdf5 entry was given"""
+ # OSError and IOError are converted to errno by fuse.ErrnoWrapper
+
+class Stat(fuse.FuseStruct): # pylint: disable-msg=R0903
+ "Fill up stat attributes with user acces"
+ st_mode = None
+ st_ino = 0
+ st_dev = 0
+ st_nlink = None
+ st_uid = os.getuid()
+ st_gid = os.getgid()
+ st_size = 0
+ st_atime = 0
+ st_mtime = 0
+ st_ctime = 0
+
+
+class GroupStat(Stat): # pylint: disable-msg=R0903
+ "Fill up stat attributes with user acces for Groups"
+ st_mode = stat.S_IFDIR | stat.S_IREAD | stat.S_IEXEC
+ st_nlink = 1
+
+
+class DatasetStat(Stat): # pylint: disable-msg=R0903
+ "Fill up stat attributes with user acces for Groups"
+ st_mode = stat.S_IFREG | stat.S_IREAD
+ st_nlink = 1
+
+
+# TODO: h5file.id.get_comment() ...
+class FsMixin(object):
+ '''Implement the HDF5 filesytem interface'''
+
+ h5file = None
+
+ def _get_entry(self, path):
+ '''Return the entry recorded at ``path`` or raise H5IOError'''
+ try:
+ h5entry = self.h5file[path]
+ except KeyError:
+ raise H5IOError(errno.ENOENT, 'No such Dataset or Group', path)
+ return h5entry
+
+ @staticmethod
+ def _readdir(h5entry, offset):
+ '''A fonction generator that yields entries from the h5 entry'''
+ for name in ('.', '..'):
+ yield fuse.Direntry(name)
+ for content in h5entry.itervalues():
+ yield fuse.Direntry(get_name(content), offset=offset)
+
+ def readdir(self, path, offset):
+ '''Return a generator that yields entries from the given Group path'''
+ return self._readdir(self._get_entry(path), offset)
+
+ def getattr(self, path):
+ '''Return the stat attributes of the given ``path``'''
+ return get_stat(self._get_entry(path))
+
+
+def get_stat(h5entry):
+ '''Return the entry stat information (an instance of Stat)'''
+ if isinstance(h5entry, h5py.highlevel.Group):
+ return GroupStat()
+ if isinstance(h5entry, h5py.highlevel.Dataset):
+ return DatasetStat()
+ raise H5IOError(errno.ENOENT, 'Unknown entry type', h5entry.name)
+
+def get_name(h5entry):
+ '''Return the entry file name as string'''
+ # XXX unicode seems not to be allowed
+ return str(osp.basename(h5entry.name))
+
+def __main__():
+ '''Main entry point that start the HDF5 filesystem server'''
+ usage = "\nUserspace HDF5 file explorer.\n\n%prog sourcefile mountpoint"
+ class H5FS(FsMixin, fuse.Fuse):
+ '''merge Fuse + HDF5 interface'''
+ pass
+ server = H5FS(version="%proog " + fuse.__version__,
+ usage=usage,
+ dash_s_do='setsingle',
+ standard_mods=True,
+ fetch_mp=True)
+ server.parse(errex=1)
+ _opts, ags = server.cmdline
+ h5file = h5py.File(ags.pop(), 'r')
+ try:
+ server.h5file = h5file
+ server.main()
+ finally:
+ h5file.close()
+
+if __name__ == '__main__':
+ __main__()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/test_H5FS.py Tue Apr 03 20:34:00 2012 +0200
@@ -0,0 +1,66 @@
+import os.path as osp
+import tempfile
+import errno
+import h5py
+
+from unittest import TestCase as TC, main
+from logilab.common.testlib import within_tempdir
+
+import h5fs
+
+__folder__=osp.dirname(osp.abspath(__file__))
+
+def getdata(path):
+ return osp.join(__folder__, 'data', path)
+
+class GetStat_TC(TC):
+
+ @within_tempdir
+ def test_group(self):
+ h5file = h5py.File('file.hdf5')
+ group = h5file.create_group(u'sam')
+ self.assertTrue(isinstance(h5fs.get_stat(group), h5fs.GroupStat))
+ h5file.close()
+
+ @within_tempdir
+ def test_dataset(self):
+ h5file = h5py.File('file.hdf5')
+ group = h5file.create_dataset(u'sam', [])
+ self.assertTrue(isinstance(h5fs.get_stat(group), h5fs.DatasetStat))
+ h5file.close()
+
+class FsMixin_TC(TC):
+ def init(self):
+ h5file = h5py.File('file.hdf5')
+ h5file.create_group(u'sam')
+ h5file.create_dataset(u'melu', (0,))
+ h5mx = h5fs.FsMixin()
+ h5mx.h5file = h5file
+ return h5file, h5mx
+
+ @within_tempdir
+ def test_readdir(self):
+ h5file, h5mx = self.init()
+ result = list(h5mx.readdir(u'/', 0))
+ expected = list(h5file[u'/'].keys()) + ['.', '..']
+ self.assertEqual(len(expected), len(result))
+
+ @within_tempdir
+ def test_readdir_wrong(self):
+ h5file, h5mx = self.init()
+ self.assertRaises(h5fs.H5IOError, h5mx.readdir, u'/wrong data path', 0)
+
+ @within_tempdir
+ def test_getattr(self):
+ h5file, h5mx = self.init()
+ self.assertTrue(isinstance(h5mx.getattr('/'), h5fs.GroupStat))
+ self.assertTrue(isinstance(h5mx.getattr('/sam'), h5fs.GroupStat))
+ self.assertTrue(isinstance(h5mx.getattr('/melu'), h5fs.DatasetStat))
+
+ @within_tempdir
+ def test_getattr_wrong(self):
+ h5file, h5mx = self.init()
+ self.assertRaises(h5fs.H5IOError, h5mx.getattr, u'/wrong data path')
+
+if __name__ == '__main__':
+ main()