refact: split h5fs.py into modules in the ``h5fslib`` package draft
authoralain leufroy <alain@leufroy.fr>
Wed, 03 Apr 2013 11:09:48 +0200
changeset 61 d7ad55206a47
parent 60 65e48edd25cb
child 62 b631988c2e9c
refact: split h5fs.py into modules in the ``h5fslib`` package This changeset just moves codes. ..note: This change simplify the implementation of other export formats as core specific code is more identifiable.
bin/h5fs
h5fs.py
h5fslib/__init__.py
h5fslib/core.py
h5fslib/dsetbitstream.py
h5fslib/dsetnpy.py
setup.py
--- a/bin/h5fs	Mon Apr 08 10:20:00 2013 +0200
+++ b/bin/h5fs	Wed Apr 03 11:09:48 2013 +0200
@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU General Public License
 # along with h5fs.  If not, see <http://www.gnu.org/licenses/>.
 
-from h5fs import main
+from h5fslib import main
 
 if __name__ == '__main__':
     main()
--- a/h5fs.py	Mon Apr 08 10:20:00 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,224 +0,0 @@
-#! /usr/bin/env python
-
-# Copyright (C) 2012-2013  Alain Leufroy <alain.leufroy@logilab.fr>
-#
-# This file is part of h5fs.
-#
-# h5fs is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# h5fs is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with h5fs.  If not, see <http://www.gnu.org/licenses/>.
-
-'''
-HDF5 filesystem implementation.
-'''
-
-import os
-import os.path as osp
-import stat
-import errno
-from itertools import imap, chain
-from functools import partial
-from StringIO import StringIO
-
-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 get_size(h5entry):
-        '''Return the byte size of the file'''
-        arr = h5entry.value
-        return arr.itemsize * arr.size
-
-    def get_stat(self, 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(st_size=self.get_size(h5entry))
-        raise H5IOError(errno.ENOENT, 'Unknown entry type', h5entry.name)
-
-    @staticmethod
-    def get_name(h5entry):
-        '''Return the entry file name as string'''
-        # XXX unicode does not seem to be allowed
-        return str(osp.basename(h5entry.name))
-
-    @staticmethod
-    def get_data(h5entry, start, end):
-        '''Return a slice of data from start to end'''
-        return h5entry.value.data[start:end]
-
-    def list_group(self, group, specs=('.', '..')):
-        '''A fonction generator that yields name of the entries in the group.
-        The list is in arbitrary order with special entries ``specs``.
-        This names are used as file/folder names
-        '''
-        return chain(iter(specs), imap(self.get_name, group.itervalues()))
-
-    @staticmethod
-    def openable(h5entry):
-        '''raise H5IOError if not h5entry could not be readable'''
-        if not isinstance(h5entry, h5py.highlevel.Dataset):
-            raise H5IOError(errno.EISDIR, 'Is a Group', h5entry.name)
-
-# ===
-
-    def readdir(self, path, offset):
-        '''Return a generator that yields entries from the given Group path
-        used as file/folder contained in path'''
-        convert = partial(fuse.Direntry, offset=offset)
-        return imap(convert, self.list_group(self.get_entry(path)))
-
-    def getattr(self, path):
-        '''Return the stat attributes of the given ``path``'''
-        return self.get_stat(self.get_entry(path))
-
-    def open(self, path, flags):
-        '''Raise H%IOError if no open is not allowed.
-
-        XXX: readonly for now
-        '''
-        if (flags & os.O_RDONLY) != os.O_RDONLY:
-            raise H5IOError(errno.EACCES, 'Could not open', path)
-        return self.openable(self.get_entry(path))
-
-    def read(self, path, size, offset):
-        h5entry = self.get_entry(path)
-        if isinstance(h5entry, h5py.highlevel.Group):
-            raise H5IOError(errno.EISDIR, 'Is a Group', path)
-        return self.get_data(h5entry, offset, offset + size)
-
-class Npy10FsMixin(FsMixin):
-    '''Implement the HDF5 filesytem interface with dataset exported as
-    .npy file version 1.0
-    '''
-    # XXX: There is a problematic case when a subgroup named
-    # 'a.npy' and a dataset named 'a' have the same parent
-    file_extension = 'npy'
-
-    @staticmethod
-    def _get_npy_header(h5entry):
-        from numpy.lib.format import write_array_header_1_0 as hwrite, \
-                                     header_data_from_array_1_0 as hdata, \
-                                     magic
-        header = StringIO()
-        header.write(magic(1, 0)) # XXX: support only 1.0 version of npy file
-        hwrite(header, hdata(h5entry.value))
-        return header.getvalue()
-
-    # ===
-
-    def get_entry(self, path):
-        '''Return the entry recorded at ``path`` or raise H5IOError'''
-        trailling = osp.extsep + self.file_extension
-        if path.endswith(trailling):
-            path = path[:-len(trailling)]
-        return super(Npy10FsMixin, self).get_entry(path)
-
-    def get_name(self, h5entry):
-        '''Return the entry file name as string'''
-        # XXX unicode deos not seems to be allowed
-        name = super(Npy10FsMixin, self).get_name(h5entry)
-        if isinstance(h5entry, h5py.highlevel.Dataset):
-            name = osp.extsep.join((name, self.file_extension))
-        return name
-
-    def get_size(self, h5entry):
-        '''Return the byte size of the file'''
-        header_size = len(self._get_npy_header(h5entry))
-        data_size = super(Npy10FsMixin, self).get_size(h5entry)
-        return header_size + data_size
-
-    def get_data(self, h5entry, start, end):
-        '''Return a slice of data data from start to end'''
-        header = self._get_npy_header(h5entry)
-        header_size = len(header)
-        data = []
-        if start < header_size:
-            data.append(header[start:end])
-            start = 0
-        else:
-            start -= header_size
-        end -= header_size
-        if end > 0:
-            data.append(super(Npy10FsMixin, self).get_data(h5entry, start, end))
-        return ''.join(data)
-
-def main(mixincls=Npy10FsMixin):
-    '''Main entry point that start the HDF5 filesystem server'''
-    usage = "\nUserspace HDF5 file explorer.\n\n%prog sourcefile mountpoint"
-    class H5FS(mixincls, fuse.Fuse):
-        '''merge Fuse + HDF5 interface'''
-        pass
-    server = H5FS(version="%prog " + 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()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/h5fslib/__init__.py	Wed Apr 03 11:09:48 2013 +0200
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+
+# Copyright (C) 2012-2013  Alain Leufroy <alain.leufroy@logilab.fr>
+#
+# This file is part of h5fs.
+#
+# h5fs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# h5fs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with h5fs.  If not, see <http://www.gnu.org/licenses/>.
+
+'''
+HDF5 filesystem main function.
+'''
+import h5py
+import fuse
+fuse.fuse_python_api = (0, 2)
+
+from h5fslib.dsetnpy import Npy10FsMixin
+
+__all__ = ['main']
+
+def main(mixincls=Npy10FsMixin):
+    '''Main entry point that start the HDF5 filesystem server'''
+    usage = "\nUserspace HDF5 file explorer.\n\n%prog sourcefile mountpoint"
+    class H5FS(mixincls, fuse.Fuse):
+        '''merge Fuse + HDF5 interface'''
+        pass
+    server = H5FS(version="%prog " + 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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/h5fslib/core.py	Wed Apr 03 11:09:48 2013 +0200
@@ -0,0 +1,145 @@
+#! /usr/bin/env python
+
+# Copyright (C) 2012-2013  Alain Leufroy <alain.leufroy@logilab.fr>
+#
+# This file is part of h5fs.
+#
+# h5fs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# h5fs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with h5fs.  If not, see <http://www.gnu.org/licenses/>.
+
+'''
+HDF5 filesystem implementation.
+'''
+
+import os
+import os.path as osp
+import stat
+import errno
+from itertools import imap, chain
+from functools import partial
+
+
+import h5py
+
+import fuse
+
+__all__ = ['FsMixin']
+
+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 hdf5 entry exposed at ``path`` in the fs or raise
+        H5IOError'''
+        try:
+            h5entry = self.h5file[path]
+        except KeyError:
+            raise H5IOError(errno.ENOENT, 'No such Dataset or Group', path)
+        return h5entry
+
+    @staticmethod
+    def get_name(h5entry):
+        '''Return the entry file name in the fs as string of the hdf5 entry'''
+        # XXX unicode does not seem to be allowed
+        return str(osp.basename(h5entry.name))
+
+    @staticmethod
+    def get_size(h5entry):
+        '''Return the byte size of the file'''
+        raise NotImplementedError
+
+    @staticmethod
+    def get_data(h5entry, start, end):
+        '''Return a slice of data from start to end'''
+        raise NotImplementedError
+
+    def get_stat(self, 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(st_size=self.get_size(h5entry))
+        raise H5IOError(errno.ENOENT, 'Unknown entry type', h5entry.name)
+
+    def list_group(self, group, specs=('.', '..')):
+        '''A fonction generator that yields name of the entries in the group.
+        The list is in arbitrary order with special entries ``specs``.
+        This names are used as file/folder names
+        '''
+        return chain(iter(specs), imap(self.get_name, group.itervalues()))
+
+    @staticmethod
+    def openable(h5entry):
+        '''raise H5IOError if not h5entry could not be readable'''
+        if not isinstance(h5entry, h5py.highlevel.Dataset):
+            raise H5IOError(errno.EISDIR, 'Is a Group', h5entry.name)
+
+# ===
+
+    def readdir(self, path, offset):
+        '''Return a generator that yields entries from the given Group path
+        used as file/folder contained in path'''
+        convert = partial(fuse.Direntry, offset=offset)
+        return imap(convert, self.list_group(self.get_entry(path)))
+
+    def getattr(self, path):
+        '''Return the stat attributes of the given ``path``'''
+        return self.get_stat(self.get_entry(path))
+
+    def open(self, path, flags):
+        '''Raise H%IOError if no open is not allowed.
+
+        XXX: readonly for now
+        '''
+        if (flags & os.O_RDONLY) != os.O_RDONLY:
+            raise H5IOError(errno.EACCES, 'Could not open', path)
+        return self.openable(self.get_entry(path))
+
+    def read(self, path, size, offset):
+        h5entry = self.get_entry(path)
+        if isinstance(h5entry, h5py.highlevel.Group):
+            raise H5IOError(errno.EISDIR, 'Is a Group', path)
+        return self.get_data(h5entry, offset, offset + size)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/h5fslib/dsetbitstream.py	Wed Apr 03 11:09:48 2013 +0200
@@ -0,0 +1,41 @@
+#! /usr/bin/env python
+
+# Copyright (C) 2012-2013  Alain Leufroy <alain.leufroy@logilab.fr>
+#
+# This file is part of h5fs.
+#
+# h5fs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# h5fs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with h5fs.  If not, see <http://www.gnu.org/licenses/>.
+
+'''
+HDF5 filesystem implementation.
+'''
+from h5fslib.core import FsMixin
+
+
+__all__ = ['BitStreamFsMixin']
+
+class BitStreamFsMixin(FsMixin):
+    '''Implement the HDF5 filesytem interface with dataset exported as bitstream
+    file.'''
+
+    @staticmethod
+    def get_size(h5entry):
+        '''Return the byte size of the file'''
+        arr = h5entry.value
+        return arr.itemsize * arr.size
+
+    @staticmethod
+    def get_data(h5entry, start, end):
+        '''Return a slice of data from start to end'''
+        return h5entry.value.data[start:end]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/h5fslib/dsetnpy.py	Wed Apr 03 11:09:48 2013 +0200
@@ -0,0 +1,87 @@
+#! /usr/bin/env python
+
+# Copyright (C) 2012-2013  Alain Leufroy <alain.leufroy@logilab.fr>
+#
+# This file is part of h5fs.
+#
+# h5fs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# h5fs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with h5fs.  If not, see <http://www.gnu.org/licenses/>.
+
+'''
+HDF5 filesystem implementation with datasets as .npy files
+'''
+import os.path as osp
+from StringIO import StringIO
+import h5py
+
+from numpy.lib.format import write_array_header_1_0 as hwrite, \
+                             header_data_from_array_1_0 as hdata, \
+                             magic
+
+from h5fslib.dsetbitstream import BitStreamFsMixin
+
+
+__all__ = ['Npy10FsMixin']
+
+class Npy10FsMixin(BitStreamFsMixin):
+    '''Implement the HDF5 filesytem interface with dataset exported as
+    .npy file version 1.0
+    '''
+    # XXX: There is a problematic case when a subgroup named
+    # 'a.npy' and a dataset named 'a' have the same parent
+    file_extension = 'npy'
+
+    @staticmethod
+    def _get_npy_header(h5entry):
+        header = StringIO()
+        header.write(magic(1, 0)) # XXX: support only 1.0 version of npy file
+        hwrite(header, hdata(h5entry.value))
+        return header.getvalue()
+
+    # ===
+
+    def get_entry(self, path):
+        '''Return the entry recorded at ``path`` or raise H5IOError'''
+        trailling = osp.extsep + self.file_extension
+        if path.endswith(trailling):
+            path = path[:-len(trailling)]
+        return super(Npy10FsMixin, self).get_entry(path)
+
+    def get_name(self, h5entry):
+        '''Return the entry file name as string'''
+        # XXX unicode deos not seems to be allowed
+        name = super(Npy10FsMixin, self).get_name(h5entry)
+        if isinstance(h5entry, h5py.highlevel.Dataset):
+            name = osp.extsep.join((name, self.file_extension))
+        return name
+
+    def get_size(self, h5entry):
+        '''Return the byte size of the file'''
+        header_size = len(self._get_npy_header(h5entry))
+        data_size = super(Npy10FsMixin, self).get_size(h5entry)
+        return header_size + data_size
+
+    def get_data(self, h5entry, start, end):
+        '''Return a slice of data data from start to end'''
+        header = self._get_npy_header(h5entry)
+        header_size = len(header)
+        data = []
+        if start < header_size:
+            data.append(header[start:end])
+            start = 0
+        else:
+            start -= header_size
+        end -= header_size
+        if end > 0:
+            data.append(super(Npy10FsMixin, self).get_data(h5entry, start, end))
+        return ''.join(data)
--- a/setup.py	Mon Apr 08 10:20:00 2013 +0200
+++ b/setup.py	Wed Apr 03 11:09:48 2013 +0200
@@ -27,6 +27,6 @@
     author_email = 'alain.leufroy@logilab.fr',
     description = 'A virtual filesystem that exposes the content of HDF5 files',
     long_description = open("README").read(),
-    py_modules = ['h5fs'],
+    packages = ['h5fslib'],
     scripts = ['bin/h5fs'],
 )