diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/config.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,219 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.python import log + +from ConfigParser import ConfigParser +import signal +from functools import partial +try: + from collections import OrderedDict +except ImportError: + from ordereddict import OrderedDict + +from validate import vInteger +from error import NeededOption + +class MyConfigParser(ConfigParser): + """Configparser that also validate configfile. + + It is possile to restiger function, that are called, when config file is reloaded + """ + def __init__(self): + ConfigParser.__init__(self) + self.reloadList=[] + + def read(self,files): + """reads an validate configuration file""" + from offer import getProvider + r = ConfigParser.read(self, files) + for s in self.sections(): + if s == "main": + main.validate(self.items(s)) + else: + getProvider("tmp", self.get(s,'typ'), self.items(s)) + return r + + def reload_(self): + """run all registered function.""" + for f in self.reloadList: + f() + + def registerReload(self, func): + """adds **func** to reloadList. + + func ist called with no arguments. + """ + self.reloadList.append(func) + +class Option(): + """One Option in the configuration file""" + def __init__(self, validate, long="", help="", must=False, default=None): + """ + :param func validate: a validate function, it has to return the value, if valid and raise an error if not. + :param string long: long description + :param string help: the help text + :param boolean must: Is this option nessasary + :param default: default value + """ + self.validate = validate + self.long=long + self.help = help + self.must = must + self.default = default + +class Config: + """Base class for all classes, that uses option from configfile. + + If one option is valid, the attribute is created with the value of the validate function. + """ + def __init__(self, name, options=None): + """ + :param string name: section name. + :param `collections.OrderedDict` options: Orderd Dict of the configuration options (see :attr:`options`) + """ + self.name = name + + self.options = options + """Options :class:`collections.OrderedDict` for Options used in configuration file (see :class:`iro.config.Option`). Ordering of configuration fields are done by :attr:`order`. + + Sample:: + + OrderedDict([ + ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)), + ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)), + ]) + + A child class typically use update to add more options. + """ + + self._init = True + """indecates, if the config is loaded with config values.""" + + + def _read(self, cfg, write=False): + """Test or set configuration options. + + :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. + :param boolean write: test or set the option to actual object. + + :raises: :exc:`iro.error.NeededOption` + """ + c = dict(cfg) + + for o in self.options: + option = self.options[o] + try: + value = option.validate(c[o],o) + if write: + self._init = False + setattr(self,o,value) + except KeyError: + if option.must: + raise NeededOption(self.name, o) + elif write and option.default is not None: + setattr(self, o, option.default) + + def validate(self, cfg): + """Validate configuration. + + :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. + """ + self._read(cfg, False) + + def load(self, cfg): + """Loads configuration into object. + + :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. + """ + self._read(cfg, True) + + def same(self, other): + """returns ``True``, if the options of other object are the same""" + for o in self.options: + if getattr(self,o) != getattr(other,o): + return False + else: + return True + + def sampleConf(self): + """returns a sample Configuration section. + + This function also adds the long help text to the sample section. + + :return: a list of lines + """ + ret=[] + for o in self.options: + opt=self.options[o] + if opt.long: + ret.append("# "+opt.long) + if opt.must: + s= "%s = "%o + if opt.default is not None: + s += str(opt.default) + ret.append(s) + else: + ret.append("# %s = %s"%(o,opt.default)) + ret.append("") + + return ["[%s]"%self.name,]+ret + +def readConfig(): + """Read the configuration and update all registered object (see :meth:`MyConfigParser.reload_`).""" + log.msg("Reading configs.") + configParser.read(confFiles) + configParser.reload_() + if main._init: + main.load(configParser.items("main")) + else: + m = Config("main", main_options) + m.load(configParser.items("main")) + if not main.same(m): + raise Exception("Main options can't be reloaded, please restart your Application.") + +def init(): + """Load the main options.""" + configParser.read(confFiles) + main.load(configParser.items("main")) + +def registerSignal(): + '''register readConfig to SIGUSR2''' + def rC(signal, frame): + readConfig() + + signal.signal(signal.SIGUSR2,rC) + +configParser = MyConfigParser() +"""configParser to get configuration.""" + +confFiles=["~/iro.conf","/etc/iro/iro.conf"] +"""Configfile list """ + +main_options = OrderedDict([ + ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)), + ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)), + ]) + +"options for main section" + +main = Config("main", main_options) +"""Main config options"""