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.
--- 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'],
)