1 # Copyright (C) 2011 W. Trevor King <wking@drexel.edu>
3 # This file is part of h5config.
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.
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.
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/>.
18 """Tools for setting up a package using config files.
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:
28 import logging as _logging
29 import os.path as _os_path
32 from . import config as _config
33 from . import log as _log
34 from . import util as _util
37 class PackageConfig (_config.Config):
38 """Configure package operation
40 This basic implementation just creates and manages a package-wide
41 `LOG` instance. If you create this instance on your own (for
42 example, to work around bootstrapping issues), just pass your
43 instance in as `logger` when you initialize this class.
45 Because this class occasionally replaces itself, you should always
46 work with the version in the package namespace and not with a
47 local reference (which may be stale).
49 Things get a bit interesting because we want to configure the
50 package from an internal class. This leads to TODO
52 _backed_subclasses = ()
54 _config.ChoiceSetting(
56 help='Module logging level.',
57 default=_logging.WARN,
59 ('critical', _logging.CRITICAL),
60 ('error', _logging.ERROR),
61 ('warn', _logging.WARN),
62 ('info', _logging.INFO),
63 ('debug', _logging.DEBUG),
65 _config.BooleanSetting(
67 help='Log to syslog (otherwise log to stderr).',
71 def __init__(self, package_name, namespace=None, logger=None, **kwargs):
72 super(PackageConfig, self).__init__(**kwargs)
73 self._package_name = package_name
75 namespace = _sys.modules[package_name]
76 self._namespace = namespace
78 logger = _log.get_basic_logger(package_name, level=_logging.WARN)
82 "Find this instance's name in the bound namespace"
83 for attr_name in dir(self._namespace):
84 attr = getattr(self._namespace, attr_name, None)
85 if id(attr) == id(self):
87 raise IndexError('{} not found in the {} namespace'.format(
88 self, self._namespace))
90 def _replace_self(self, new_config):
91 self._logger.debug('replacing {} package config {} with {}'.format(
92 self._package_name, self, new_config))
95 setattr(self._namespace, name, new_config)
98 self._logger.setLevel(self['log-level'])
100 if 'syslog' not in self._logger._handler_cache:
101 _syslog_handler = _logging_handlers.SysLogHandler()
102 _syslog_handler.setLevel(_logging.DEBUG)
103 self._logger._handler_cache['syslog'] = _syslog_handler
104 self._logger.handlers = [self._logger._handler_cache['syslog']]
106 self._logger.handlers = [self._logger._handler_cache['stream']]
107 self._logger.info('setup {} packge config:\n{}'.format(
108 self._package_name, self.dump()))
111 "Replace self with a non-backed version with default settings."
112 replacement = self._clear_class(
113 package_name=self._package_name,
114 namespace=self._namespace,
115 logger = self._logger)
116 self._replace_self(replacement)
118 def _base_paths(self):
119 user_basepath = _os_path.join(
120 _os_path.expanduser('~'), '.{}rc'.format(self._package_name))
121 system_basepath = _os_path.join('/etc', self._package_name, 'config')
122 distributed_basepath = _os_path.join(
123 '/usr', 'share', self._package_name, 'config')
124 return [user_basepath, system_basepath, distributed_basepath]
126 def load_system(self):
127 "Return the best `PackageConfig` match after scanning the filesystem"
128 self._logger.info('looking for package config file')
129 basepaths = self._base_paths()
130 for basepath in basepaths:
131 for extension,config in self._backed_subclasses:
132 filename = basepath + extension
133 if _os_path.exists(filename):
135 'base_config file found at {}'.format(filename))
136 replacement = config(
138 package_name=self._package_name,
139 namespace=self._namespace,
140 logger = self._logger)
142 self._replace_self(replacement)
146 'no base_config file at {}'.format(filename))
147 # create (but don't save) the preferred file
148 basepath = basepaths[0]
149 extension,config = self._backed_subclasses[0]
150 filename = basepath + extension
151 self._logger.info('new base_config file at {}'.format(filename))
152 replacement = config(
154 package_name=self._package_name,
155 namespace=self._namespace,
156 logger = self._logger)
157 self._replace_self(replacement)
160 PackageConfig._clear_class = PackageConfig
163 _util.build_backend_classes(_sys.modules[__name__])
165 PackageConfig._backed_subclasses = [
166 ('.h5', HDF5_PackageConfig),
167 ('.yaml', YAML_PackageConfig)