Add ability to nest Configs.
[h5config.git] / h5config / tools.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 """Tools for setting up a package using config files.
19
20 The benefit of subclassing `PackageConfig` over using something like
21 `configparser` is that you can easily store default `h5config` values
22 in the configuration file.  Consider the following example:
23
24 TODO
25 > class _MyConfig
26 """
27
28 import logging as _logging
29 import os.path as _os_path
30 import sys as _sys
31
32 from . import config as _config
33 from . import log as _log
34 from .storage.hdf5 import HDF5_Storage as _HDF5_Storage
35 from .storage.yaml import YAML_Storage as _YAML_Storage
36
37
38 class PackageConfig (_config.Config):
39     """Configure package operation
40
41     This basic implementation just creates and manages a package-wide
42     `LOG` instance.  If you create this instance on your own (for
43     example, to work around bootstrapping issues), just pass your
44     instance in as `logger` when you initialize this class.
45     """
46     possible_storage = [_HDF5_Storage, _YAML_Storage]
47     settings = [
48         _config.ChoiceSetting(
49             name='log-level',
50             help='Module logging level.',
51             default=_logging.WARN,
52             choices=[
53                 ('critical', _logging.CRITICAL),
54                 ('error', _logging.ERROR),
55                 ('warn', _logging.WARN),
56                 ('info', _logging.INFO),
57                 ('debug', _logging.DEBUG),
58                 ]),
59         _config.BooleanSetting(
60             name='syslog',
61             help='Log to syslog (otherwise log to stderr).',
62             default=False),
63         ]
64
65     def __init__(self, package_name, namespace=None, logger=None, **kwargs):
66         super(PackageConfig, self).__init__(**kwargs)
67         self._package_name = package_name
68         if not namespace:
69             namespace = _sys.modules[package_name]
70         self._namespace = namespace
71         if not logger:
72             logger = _log.get_basic_logger(package_name, level=_logging.WARN)
73         self._logger = logger
74         if 'LOG' not in dir(namespace):
75             namespace.LOG = logger
76
77     def setup(self):
78         self._logger.setLevel(self['log-level'])
79         if self['syslog']:
80             if 'syslog' not in self._logger._handler_cache:
81                 _syslog_handler = _logging_handlers.SysLogHandler()
82                 _syslog_handler.setLevel(_logging.DEBUG)
83                 self._logger._handler_cache['syslog'] = _syslog_handler
84                 self._logger.handlers = [self._logger._handler_cache['syslog']]
85         else:
86             self._logger.handlers = [self._logger._handler_cache['stream']]
87         self._logger.info('setup {} packge config:\n{}'.format(
88                 self._package_name, self.dump()))
89
90     def clear(self):
91         "Replace self with a non-backed version with default settings."
92         super(PackageConfig, self).clear()
93         self._storage = None
94
95     def _base_paths(self):
96         user_basepath = _os_path.join(
97             _os_path.expanduser('~'), '.{}rc'.format(self._package_name))
98         system_basepath = _os_path.join('/etc', self._package_name, 'config')
99         distributed_basepath =  _os_path.join(
100             '/usr', 'share', self._package_name, 'config')
101         return [user_basepath, system_basepath, distributed_basepath]
102
103     def load_system(self):
104         "Return the best `PackageConfig` match after scanning the filesystem"
105         self._logger.info('looking for package config file')
106         basepaths = self._base_paths()
107         for basepath in basepaths:
108             for storage in self.possible_storage:
109                 filename = '{}.{}'.format(basepath, storage.extension)
110                 if _os_path.exists(filename):
111                     self._logger.info(
112                         'base_config file found at {}'.format(filename))
113                     self._storage = storage(filename=filename)
114                     self.load()
115                     self.setup()
116                     return
117                 else:
118                     self._logger.debug(
119                         'no base_config file at {}'.format(filename))
120         # create (but don't save) the preferred file
121         basepath = basepaths[0]
122         storage = self.possible_storage[0]
123         filename = '{}.{}'.format(basepath, storage.extension)
124         self._logger.info('new base_config file at {}'.format(filename))
125         self._storage = storage(filename=filename)
126         self.load()
127         self.setup()