minimal implementation: allow to display the data tree draft
authorAlain Leufroy <alain.leufroy@logilab.fr>
Tue, 03 Apr 2012 20:34:00 +0200
changeset 0 03d0140deb6c
child 14 f4791bdd245a
minimal implementation: allow to display the data tree :: python h5fs.py path/to/hdf5file.hdf5 /path/to/mountpoint tree /path/to/mountpoint
h5fs.py
test/test_H5FS.py
--- /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()