1 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
3 # This file is part of h5config.
5 # h5config is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
10 # h5config is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # h5config. If not, see <http://www.gnu.org/licenses/>.
17 """HDF5 backend implementation
20 import os.path as _os_path
24 import numpy as _numpy
26 from .. import LOG as _LOG
27 from .. import config as _config
28 from . import FileStorage as _FileStorage
29 from . import is_string as _is_string
32 def pprint_HDF5(*args, **kwargs):
33 print(pformat_HDF5(*args, **kwargs))
35 def pformat_HDF5(filename, group='/'):
37 with _h5py.File(filename, 'r') as f:
39 ret = '\n'.join(_pformat_hdf5(cwg))
41 if 'unable to open' in e.message:
42 if _os_path.getsize(filename) == 0:
48 def _pformat_hdf5(cwg, depth=0):
50 lines.append(' '*depth + cwg.name)
52 for key,value in cwg.items():
53 if isinstance(value, _h5py.Group):
54 lines.extend(_pformat_hdf5(value, depth))
55 elif isinstance(value, _h5py.Dataset):
56 lines.append(' '*depth + str(value))
57 lines.append(' '*(depth+1) + str(value[...]))
59 lines.append(' '*depth + str(value))
62 def h5_create_group(cwg, path, force=False):
63 "Create the group where the settings are stored (if necessary)."
67 for group in path.strip('/').split('/'):
69 if group not in cwg.keys():
70 _LOG.debug('creating group {} in {}'.format(
71 '/'.join(gpath), cwg.file))
72 cwg.create_group(group)
74 if isinstance(_cwg, _h5py.Dataset):
76 _LOG.info('overwrite {} in {} ({}) with a group'.format(
77 '/'.join(gpath), _cwg.file, _cwg))
79 _cwg = cwg.create_group(group)
81 raise ValueError(_cwg)
86 class HDF5_Storage (_FileStorage):
87 """Back a `Config` class with an HDF5 file.
89 The `.save` and `.load` methods have an optional `group` argument
90 that allows you to save and load settings from an externally
91 opened HDF5 file. This can make it easier to stash several
92 related `Config` classes in a single file. For example
96 >>> from ..test import TestConfig
97 >>> fd,filename = tempfile.mkstemp(
98 ... suffix='.'+HDF5_Storage.extension, prefix='pypiezo-')
101 >>> f = _h5py.File(filename, 'a')
102 >>> c = TestConfig(storage=HDF5_Storage(
103 ... filename='untouched_file.h5', group='/untouched/group'))
104 >>> c['alive'] = True
105 >>> group = f.create_group('base')
106 >>> c.save(group=group)
107 >>> pprint_HDF5(filename) # doctest: +REPORT_UDIFF, +ELLIPSIS
110 <HDF5 dataset "age": shape (), type "<f8">
112 <HDF5 dataset "alive": shape (), type "|b1">
114 <HDF5 dataset "bids": shape (3,), type "<f8">
116 <HDF5 dataset "children": shape (), type "|S1">
118 <HDF5 dataset "claws": shape (2,), type "<i8">
120 <HDF5 dataset "daisies": shape (), type "<i...">
122 <HDF5 dataset "name": shape (), type "|S1">
124 <HDF5 dataset "species": shape (), type "|S14">
126 <HDF5 dataset "spouse": shape (), type "|S1">
128 <HDF5 dataset "words": shape (2,), type "|S7">
133 >>> c.load(group=group)
138 >>> os.remove(filename)
142 def __init__(self, group='/', **kwargs):
143 super(HDF5_Storage, self).__init__(**kwargs)
144 if isinstance(group, _h5py.Group):
145 self._file_checked = True
147 assert group.startswith('/'), group
148 if not group.endswith('/'):
150 self._file_checked = False
153 def _check_file(self):
154 if self._file_checked:
157 self._file_checked = True
159 def _setup_file(self):
160 self._create_basedir(filename=self._filename)
161 with _h5py.File(self._filename, 'a') as f:
162 cwg = f # current working group
163 h5_create_group(cwg, self.group)
165 def _load(self, config, group=None):
169 if isinstance(self.group, _h5py.Group):
173 f = _h5py.File(self._filename, 'r')
174 group = f[self.group]
175 for s in config.settings:
176 if s.name not in group.keys():
178 if isinstance(s, _config.ConfigListSetting):
180 cwg = h5_create_group(group, s.name)
185 for i in sorted(int(x) for x in cwg.keys()):
186 instance = s.config_class()
188 _cwg = h5_create_group(cwg, str(i))
192 self._load(config=instance, group=_cwg)
193 value.append(instance)
194 config[s.name] = value
195 elif isinstance(s, _config.ConfigSetting):
197 cwg = h5_create_group(group, s.name)
201 if not config[s.name]:
202 config[s.name] = s.config_class()
203 self._load(config=config[s.name], group=cwg)
206 v = group[s.name][...]
207 except Exception as e:
208 _LOG.error('Could not access {}/{}: {}'.format(
209 group.name, s.name, e))
211 if isinstance(v, _numpy.ndarray):
212 if isinstance(s, _config.BooleanSetting):
213 v = bool(v) # array(True, dtype=bool) -> True
214 elif v.dtype.type == _numpy.string_:
215 if isinstance(s, _config.ListSetting):
220 if _sys.version_info >= (3,):
221 for i,v_ in enumerate(v):
222 if isinstance(v_, bytes):
223 v[i] = str(v_, 'utf-8')
224 else: # array('abc', dtype='|S3') -> 'abc'
225 if _sys.version_info >= (3,):
229 elif isinstance(s, _config.IntegerSetting):
230 v = int(v) # array(3, dtpe='int32') -> 3
231 elif isinstance(s, _config.FloatSetting):
232 v = float(v) # array(1.2, dtype='float64') -> 1.2
233 elif isinstance(s, _config.NumericSetting):
234 raise NotImplementedError(type(s))
235 elif isinstance(s, _config.ListSetting):
236 v = list(v) # convert from numpy array
238 # convert back from None, etc.
239 v = s.convert_from_text(v)
245 def _save(self, config, group=None):
249 if isinstance(self.group, _h5py.Group):
253 f = _h5py.File(self._filename, 'a')
254 group = f[self.group]
255 for s in config.settings:
257 if isinstance(s, (_config.BooleanSetting,
258 _config.NumericSetting,
259 _config.ListSetting)):
260 value = config[s.name]
261 if value in [None, []]:
262 value = s.convert_to_text(value)
263 elif isinstance(s, _config.ConfigListSetting):
264 configs = config[s.name]
266 cwg = h5_create_group(group, s.name, force=True)
267 for i,cfg in enumerate(configs):
268 _cwg = h5_create_group(cwg, str(i), force=True)
269 self._save(config=cfg, group=_cwg)
271 elif isinstance(s, _config.ConfigSetting):
274 cwg = h5_create_group(group, s.name, force=True)
275 self._save(config=cfg, group=cwg)
277 if value is None: # not set yet, or invalid
278 value = s.convert_to_text(config[s.name])
279 if _sys.version_info >= (3,): # convert strings to bytes/
280 if isinstance(value, str):
281 value = value.encode('utf-8')
282 elif isinstance(value, list):
283 value = list(value) # shallow copy
284 for i,v in enumerate(value):
285 if isinstance(v, str):
286 value[i] = v.encode('utf-8')
292 group[s.name] = value
294 raise ValueError((value, type(value)))