iro/config.py
changeset 302 3f4bdea2abbf
parent 294 0e75bd39767d
child 306 b31978c0c043
--- /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 <iro@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"""