4e06567ff8896322b7e6ec717185f70a90e16199
[h5config.git] / h5config / hdf5.py
1 # Copyright (C) 2011 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of h5config.
4 #
5 # h5config is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
9 #
10 # h5config is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with h5config.  If not, see <http://www.gnu.org/licenses/>.
17
18 """HDF5 backend implementation
19 """
20
21 import h5py as _h5py
22
23 from . import LOG as _LOG
24 from . import config as _config
25
26
27 def pprint_HDF5(*args, **kwargs):
28     print pformat_HDF5(*args, **kwargs)
29
30 def pformat_HDF5(filename, group='/'):
31     f = _h5py.File(filename, 'r')
32     cwg = f[group]
33     return '\n'.join(_pformat_hdf5(cwg))
34
35 def _pformat_hdf5(cwg, depth=0):
36     lines = []
37     lines.append('  '*depth + cwg.name)
38     depth += 1 
39     for key,value in cwg.iteritems():
40         if isinstance(value, _h5py.Group):
41             lines.extend(_pformat_hdf5(value, depth))
42         elif isinstance(value, _h5py.Dataset):
43             lines.append('  '*depth + str(value))
44             lines.append('  '*(depth+1) + str(value[...]))
45         else:
46             lines.append('  '*depth + str(value))
47     return lines
48                          
49
50 class _HDF5Config (_config.BackedConfig):
51     """Mixin to back a `_Config` class with an HDF5 file.
52
53     TODO: Special handling for Choice (enums), FloatList (arrays), etc.?
54
55     The `.save` and `.load` methods have an optional `group` argument
56     that allows you to save and load settings from an externally
57     opened HDF5 file.  This can make it easier to stash several
58     related `_Config` classes in a single file.  For example
59
60     >>> import os
61     >>> import tempfile
62     >>> from .test import HDF5_TestConfig
63     >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='pypiezo-')
64     >>> os.close(fd)
65
66     >>> f = _h5py.File(filename, 'a')
67     >>> c = HDF5_TestConfig(filename='untouched_file.h5',
68     ...                     group='/untouched/group')
69     >>> c['syslog'] = True
70     >>> group = f.create_group('base')
71     >>> c.save(group)
72     >>> pprint_HDF5(filename)
73     /
74       /base
75         <HDF5 dataset "age": shape (), type "|S3">
76           1.3
77         <HDF5 dataset "alive": shape (), type "|S2">
78           no
79         <HDF5 dataset "bids": shape (), type "|S11">
80           5.4, 3.2, 1
81         <HDF5 dataset "daisies": shape (), type "|S2">
82           13
83         <HDF5 dataset "species": shape (), type "|S14">
84           Norwegian Blue
85     >>> d = HDF5_TestConfig(filename='untouched_file.h5',
86     ...                     group='/untouched/group')
87     >>> d.load(group)
88     >>> d['alive']
89     False
90
91     >>> f.close()
92     >>> os.remove(filename)
93     """
94     def __init__(self, filename=None, group='/', **kwargs):
95         super(_HDF5Config, self).__init__(**kwargs)
96         self.filename = filename
97         assert group.startswith('/'), group
98         if not group.endswith('/'):
99             group += '/'
100         self.group = group
101         self._file_checked = False
102
103     def _check_file(self):
104         if self._file_checked:
105             return
106         self._setup_file()
107         self._file_checked = True
108
109     def _setup_file(self):
110         f = _h5py.File(self.filename, 'a')
111         cwg = f  # current working group
112
113         # Create the group where the settings are stored (if necessary)
114         gpath = ['']
115         for group in self.group.strip('/').split('/'):
116             if not group:
117                 break
118             gpath.append(group)
119             if group not in cwg.keys():
120                 _LOG.debug('creating group %s in %s'
121                            % ('/'.join(gpath), self.filename))
122                 cwg.create_group(group)
123             cwg = cwg[group]
124         f.close()
125
126     def dump(self, help=False, from_file=False):
127         """Return the relevant group in `self.filename` as a string
128
129         Extends the base :meth:`dump` by adding the `from_file`
130         option.  If `from_file` is true, dump all entries that
131         currently exist in the relevant group, rather than listing all
132         settings defined in the instance dictionary.
133         """
134         if from_file:
135             self._check_file()
136             f = _h5py.File(self.filename, 'r')
137             cwg = f[self.group]
138             lines = []
139             settings = dict([(s.name, s) for s in self.settings])
140             for key,value in cwg.iteritems():
141                 if help and key in settings:
142                     help_string = '\t(%s)' % settings[key].help()
143                 else:
144                     help_string = ''
145                 lines.append('%s: %s%s' % (key, value[...], help_string))
146             return '\n'.join(lines)
147         return super(_HDF5Config, self).dump(help=help)
148
149     def load(self, group=None):
150         if group is None:
151             self._check_file()
152             f = _h5py.File(self.filename, 'r')
153             group = f[self.group]
154         else:
155             f = None
156         for s in self.settings:
157             if s.name not in group.keys():
158                 continue
159             self[s.name] = s.convert_from_text(group[s.name][...])
160         if f:
161             f.close()
162
163     def save(self, group=None):
164         if group is None:
165             self._check_file()
166             f = _h5py.File(self.filename, 'a')
167             group = f[self.group]
168         else:
169             f = None
170         for s in self.settings:
171             value = s.convert_to_text(self[s.name])
172             try:
173                 del group[s.name]
174             except KeyError:
175                 pass
176             group[s.name] = value
177         if f:
178             f.close()