iro/config.py
author hefee
Sat, 27 Jul 2019 13:29:15 +0200
changeset 306 b31978c0c043
parent 294 0e75bd39767d
child 312 42fd5075a5d1
permissions -rw-r--r--
suche auch im Homeverzeichnis nach config Datei.

# 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

import os.path
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", os.path.expanduser("~/.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"""