Config.py :  » Web-Server » SkunkWEB » skunkweb-3.4.4 » pylibs » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Web Server » SkunkWEB 
SkunkWEB » skunkweb 3.4.4 » pylibs » Config.py
#  
#  Copyright (C) 2001 Andrew T. Csillag <drew_csillag@geocities.com>
#  
#      You may distribute under the terms of either the GNU General
#      Public License or the SkunkWeb License, as specified in the
#      README file.
#   
"""
An implementation of a general purpose 'Config' class. 
It can be used to store information pertinent to a 
configuration of any python executable.

Eventually, this module will have helper 
functions to assist parsing of 
configuration files, etc.
"""

import types
import string
import sys
import os

# Our modules
import LineWrap
import ErrorHandler
from SkunkExcept import *

# Load the logging functions
#try:
#    from AELogging import LOG,DEBUG,MEMDEBUG
#except ImportError:
#    from Logging import LOG,DEBUG
#    MEMDEBUG = lambda x:None

#
# Some useful hooks for assigning values to elements 
#
def file_path_hook ( el, v ):
    "This hook could be used to construct a list of directories"

    if type(v) == types.ListType:
        el._value = v
    elif type(v) == types.StringType:
        # Allow ':' separated entries, useful for command line
        el._value = map ( lambda p : os.path.abspath(p), 
                          string.split ( v, ':' ) )
    else:
        raise SkunkRuntimeError, \
              'element %s: invalid value for file_path_hook(): %s' % \
              (el.getName(), v)

# 
# This is a hook which assigns a value to a sub-config. Useful for setting
# command line parameters in core config
#
# Pass subconfig by name to avoid unnecessary object references
class assign_sub_config:
    def __init__(self, conf, subconf, elmt):
        self.subconf = subconf          # String name
        self.conf = conf                # Actual pointer to object
        self.elmt = elmt

    def __call__(self, el, v):
        # This creates a circular reference, which 
        # will need to be broken up
        self.conf._subconfigs[self.subconf][self.elmt] = v

# The exception we throw if cmdline parse is not successful
class ParseError ( SkunkRuntimeError ):
    pass

class Element:
    """
    This is an element that could be stored in a configuration class. 
    It is basically implemented to simplify command line parsing and to 
    enforce types on the configuration parameters

    The Element needs to be initialized with following values:

    Required:

    name              - the name of this config variable
    desc              - a short description of the variable
    value             - default value to set for this element

    Optional:

    short_op          - short options, suitable for getopt - like 'p', 'p:'
    long_op           - also for getopt, like 'port', 'port='
    value_name        - name to use for value in command line parameter
                        note that if it's given, the option is assumed to 
                        require a parameter, and not require one otherwise
    convert_hook      - the hook which is called to convert the (string?)
                        value of the option to whatever type it needs to 
                        be stored in. Most commonly just int() function
    assign_hook       - function to call when an element is assigned to. 
                        Function is called in the f ( el, val ) and 
                        return value is ignored
    readonly          - can be toggled to make element readonly after init
    firstpass         - internal switch, tells config module that this 
                        variable needs to be parsed first
    multi             - allow multiple arguments of given type on command line

    Examples:

    Element ( 'BindPort', 'the port to bind to', 9988,
              'p', 'port', 'port', convert_hook = int )
    """

    def __init__ ( self, name, desc, value, short_op = None, long_op = None, 
                        value_name = None, 
                        convert_hook = None, assign_hook = None, 
                        readonly = 0, subconfig_modify = 1,
                        firstpass = 0, multi = 0):
        # Just assign this stuff
        self._name, self._desc, self._value_name = name, desc, value_name
        self._short_op, self._long_op = short_op, long_op
        self._convert_hook, self._assign_hook = convert_hook, assign_hook

        self._readonly = 0

        # Basically, don't call any hooks at init() - it only makes it 
        # more complicated
        self._value = value

        self._readonly, self._firstpass = readonly, firstpass
        self._multi = multi

        # Just to avoid type errors later. Shouldn't be empty either
        if self._desc == None:
            self._desc = '<no description>'

        # Some sanity check, to remind people we're NOT getopt like options
        if self._short_op and len(self._short_op) > 1:
            raise ValueError, 'invalid short par "%s" given for variable %s' % \
                              ( self._short_op, self._name )

        if self._long_op and self._long_op[-1] == '=':
            raise ValueError, 'invalid long par "%s" given for variable %s' % \
                              ( self._long_op, self._name )

        # A flag indicating if elmt was set on cmd line
        self._cmdset = 0
        

    def __repr__ ( self ):
        "Print self in a debug like manner"
        return '%s = %s\n' % ( self._name, self._value )

    def setCmdLine ( self ):    self._cmdset = 1
    def isSetCmdLine ( self ):     return self._cmdset

    def setValue ( self, val ):
        "Set the value of this element"

        if self.isReadonly():
            # Complain a bit
            raise ValueError, 'assignment to readonly element'

        if self._assign_hook:
            # _assign_hook() overloads all
            try:
                self._assign_hook(self, val) 
            except SystemExit:
                # Some assign hooks could be used to indicate an exit,
                # like 'help' in command line options
                raise 
            except:
                raise SkunkRuntimeError, \
                     'error calling assign hook for config element %s : %s' % \
                     ( self._name, sys.exc_info()[1] )
        elif self._convert_hook:
            try:
                self._value = self._convert_hook ( val )
            except:
                raise SkunkRuntimeError, \
              'error calling convert function for config element %s : %s' % \
               ( self._name, sys.exc_info()[1] )
        else:
            # just assign it then
            self._value = val

    def getValue ( self ):              return self._value

    def getName ( self ):               return self._name

    def isReadonly ( self ):            return self._readonly

    def isMulti ( self ):               return self._multi

    def needsParm ( self ):             
        "Return true if option expects a parameter"

        return self._value_name != None

    def isCmdLine ( self ):
        "Return true if this option is command line configurable"

        if self._short_op or self._long_op:
            return 1
        else:
            return 0

    def paramDesc ( self ):
        "Return a string describing parameters for self"

        # Basically, return one of the following:
        # -x 
        # -x value
        # -x, --fullop=value
        # -x, --fullop
        if self.isCmdLine():
            return self._myJoin ( self.shortOp(1), self.longOp(1), ', ' )
        else:
            return ''

    def paramCmdLine ( self ):
        "return a string suitable for printing in Usage: line"

        # Like so:
        # -x
        # -x foo
        # -x foo | --extra=foo
        # -x | --extra
        if self.isCmdLine():
            return self._myJoin ( self.shortOp(1), self.longOp(1), ' | ' )
        else:
            return ''

    def _myJoin ( self, s1, s2, sep ):
        "My join"

        ret = ''
        if s1:
            ret = s1
        if s2:
            if ret:
                ret = ret + sep + s2
            else:
                ret = s2
        return ret

    def shortOp ( self, full = 0 ):
        "return the short option without the optional param char"
        if self._short_op:
            if full:  
                if self.needsParm():
                    return '-%s %s' % (self._short_op, self._value_name)
                else:
                    return '-%s' % (self._short_op)
            else:
                return self._short_op
        else:
            return None

    def longOp ( self, full = 0 ):
        "return the long option without the optional param char"
        if self._long_op:
            if full:
                if self.needsParm():
                    return '--%s=%s' % ( self._long_op, self._value_name )
                else:
                    return '--%s' % self._long_op
            return self._long_op
        else:
            return None


class Config:
    """
    This is the dictionary-like class, with functions for parsing the 
    command line, printing usage and reading config files. A config object
    is essentially a collection of elements which could be set via command
    line, config files or hard coded. 

    Each element could have a description, look for doc(ConfigElement) for 
    more info. Elements without description are free form and can be assigned
    in any way
    """
    def __init__ ( self, el_list, desc = None, exec_name = sys.argv[0],
                         fixed = 0, name = None):
        "Initialize self, giving description for each element."

        self._dict, self._fixed = {}, 0
        self._name = name

        for el in el_list:
            # Can't be too paranoid
            if type(el) != types.InstanceType and \
                                     not isinstance ( el, Element ):
                raise TypeError, 'instance of Element expected' 

            # Do some checks 
            self.addElement ( el )

        # Add a description for printing when doing usage()
        if desc:
            self._desc = string.replace ( desc, '\n', '' )
        else:
            self._desc = '<no description>'

        self._exec_name = exec_name
        
        # If fixed is set, we're not allowed to add any more elements
        self._fixed = fixed

        # This is a helper dictionary to assist cmd line parsing
        self._opts_set = {}

    def unload ( self ):
        """
        Break some references
        """
        for el in self._dict.values():
            el.__dict__.clear()

    def _element ( self, el ):
        """
        Allow people direct access to elements - discouraged generally
        """
        return self._dict[el]

    def usage ( self ):
        """Represent self in a manner suitable for printing during a 
        command line usage() function"""

        # See if there ARE any command line option
        opts = filter ( lambda x : x.isCmdLine(), self._dict.values() )

        # Be really fancy here - use LineWrap module, etc.
        maxwidth = 80

        ret = 'Usage: %s' % self._exec_name

        if not len(opts):
            # Just add description
            ret = ret + '\n\n%s' % self._desc
            return ret

        # XXX We should change this if we ever get to mutually exclusive /
        # cluster options
        opstr = string.join ( 
                 map ( lambda x : '[%s]' % x.paramCmdLine(), opts ), ' ' )

        pad = len(ret) + 1
        opstr = string.split(LineWrap.wraplines ( opstr, maxwidth - pad ), '\n')

        ret = ret + ' ' + opstr[0] + '\n';
        for l in opstr[1:]:
            ret = ret + ' ' * pad + l + '\n' 

        # Add description
        ret = ret + '\n' + self._desc + '\n'

        ret = ret + '\nCommand line options:\n\n'
        
        # First pass - find the maxlen of params 
        maxpar = max ( map (lambda x : len(x.paramDesc()), self._dict.values()))

        # Give an extra line for too long optional parameters
        if maxpar > 24:
            maxpar = 24 

        # Reserve some more chars 
        res = 6

        for el in opts:

            # LineWrap API changed, emulate old API
            desc = LineWrap.wraplines ( el._desc, maxwidth - maxpar - res )
            desc = string.split ( desc, '\n' )

            parDesc = el.paramDesc()

            if len(parDesc) > maxpar:
                ret = ret + ' %s\n' % parDesc 
                ret = ret + ' ' * (maxpar + res - 2) + '- '
            else:
                ret = ret + ' %s   - ' % string.ljust(parDesc, maxpar)

            ret = ret + desc[0] + '\n';

            for s in desc[1:]:
                ret = ret + ' ' * (maxpar + res) + s + '\n'

        return ret

    def optimize ( self ):
        """
        After the config is setup and is no longer mutable, this function 
        could be used to convert it to a plain dictionary - thus greatly
        speeding up lookups
        """
        ret = {}

        for k, el in self._dict.items():
            ret[k] = el.getValue()

        return ret

    def __repr__( self ):
        "Print self in a way suitable for debug"

        ret = 'Config: %s\n' % self._desc

        pad = '  '
        for el in self._dict.values():
            ret = ret + pad + repr ( el )

        return ret

    def __getitem__ ( self, k ):
        "Implement dictionary []"

        if not self._dict.has_key ( k ):
            raise AttributeError, '%s: no such config variable: %s' % \
                                  (self._name, k)

        return self._dict[k].getValue()

    def __setitem__ ( self, k, v ):
        "Implement dictionary [] set op"

        if not self._dict.has_key ( k ):
            # Create a new dictionary element
            self.addElement ( Element ( k, '<no description>', v ))
        else:
            self._dict[k].setValue ( v )

    def addElement ( self, el ):
        "Add an element, make sure he doesn't step on other's toes"

        if self._dict.has_key ( el.getName() ):
            raise ValueError, 'element %s already exists!' % el.getName()

        if self._fixed:
            raise SkunkRuntimeError, \
                     "attempting to add element '%s' to fixed config object"%\
                     el.getName()

        for l in self._dict.values():
            if l.shortOp() and el.shortOp() and \
               l.shortOp() == el.shortOp():
                raise ValueError, "element %s already defines option '%c'" % \
                                  (l.getName(), l.shortOp())
            elif l.longOp() and el.longOp() and \
               l.longOp() == el.longOp():
                raise ValueError, "element %s already defines option '%s'" % \
                                  (l.getName(), l.longOp())

            # Seems fine I guess...
        else:
            self._dict[el.getName()] = el

    # Parse command line. This function could be called to do complete command
    # line parsing. One can supply a hook function which will be called
    # between first and second pass of the command line arguments
    def parse ( self, args, hook = None ):
        """parse ( self, args, hook = None)
        
        Parse the command line arguments. If hook is callable call it between
        first and second passes

        On parse error, prints usage and exits

        Returns any arguments left after parsing
        """

        # Now, start parsing the command line arguments
        try:
            # First pass
            args = self._parseCmdLine ( args, firstpass = 1 )

            # Call the hook
            if callable(hook):
                hook(self)

            # Second pass
            args = self._parseCmdLine ( args )
        except ParseError:
            sys.stderr.write ( 'Error: %s\n' % sys.exc_info()[1] )
            sys.stderr.write ( self.usage() + '\n' )
            sys.exit(1)

        return args

    # Load a configuration from file 
    def loadFile ( self, fname, ns_dict = {}, chdir = 1 ):
        """Read the configuration file fname. Setup the variables
        
        nsdict contains additional variables to be put in config file's
        namespace when it is being evaluated
        
        If chdir is true, will add to syspath the directory where config file is
        prior to parsing it
        """

        # Search for the file in the pythonpath directory
        realfile = fname
        if not os.path.exists ( fname ) and not os.path.isabs ( fname ):
            for d in sys.path:
                realfile = os.path.join ( d, fname ) 
           
                if os.path.exists ( fname ):
                    break
            else:
                # To restore it back
                realfile = fname

        try:
            code = compile ( open(realfile).read(), fname, 'exec' )
        except SyntaxError, val:
            raise SkunkSyntaxError ( realfile, open(realfile).read(), val )
        except IOError: 
            raise SkunkStandardError, 'cannot read config file %s : %s' % \
                                      ( realfile, sys.exc_info()[1] )

        # Let's be nice to the config file - add the directory it's in to 
        # sys.path
        if chdir:
            _dir = os.path.dirname ( realfile )

            if _dir in sys.path:
                _dir = ''
            else:
                sys.path.insert(0, _dir)

        # Run the code in controlled namespace. Defined variables:
        # 
        # config - pointer to current configuration object
        # yes, no, true, false - various boolean values
        ns_dict['config'] = self

        for y in ( 'yes', 'true', 'YES', 'TRUE', 'Yes', 'True' ):
            ns_dict[y] = 1 
        for n in ( 'no', 'false', 'NO', 'FALSE', 'No', 'False' ):
            ns_dict[n] = 0

        ns = ns_dict.copy()

        try:
            exec code in ns
        except:
            trace = ErrorHandler.readError()

            raise SkunkStandardError, 'cannot parse config file %s :\n%s' % \
                                      ( realfile, trace )
        for k, v in ns.items():
            if type(v) not in (types.ModuleType, types.FunctionType) and \
               k not in [ '__builtins__' ] + ns_dict.keys() and k[0] != '_':
                # Check that it's not set on the command line
                if self.has_key(k) and self._dict[k].isSetCmdLine():
                    continue

                self[k] = v
                
        # Get rid of the _dir in path if we added it there
        if chdir and _dir:
            del sys.path[sys.path.index(_dir)]

        # Should be all done!

    # Make us REALLY look like a dictionary
    def items ( self ):
        return map ( lambda x: (x[0], x[1].getValue()), self._dict.items() )

    def has_key ( self, k ):
        return self._dict.has_key ( k )

    def values ( self ):
        return map ( lambda x: x.getValue(), self._dict.values() )

    # Command line parsing
    def setOpt ( self, el, val = None ):
        "Set the option. Either toggle it or set it"

        # sanity check, this should be taken care of already hence
        # raise ValueError
        if el.needsParm() and val == None:
            raise ValueError, "option '%s' requires parameter" % \
                              el.paramCmdLine()

        if not el.needsParm() and val:
            raise ValueError, "option '%s' does not require parameter" % \
                              el.paramCmdLine()

        if not el.isMulti() and self._opts_set.has_key ( el.getName() ):
            raise ParseError, "attempting to set option '%s' twice" % \
                              el.paramCmdLine()

        if not val:
            # Toggle boolean options
            val = 1

        el.setValue ( val )

        # Indicate that this parameter was set on the command line, so it 
        # is not overriden when we're reading config files
        el.setCmdLine()

    def _parseCmdLine ( self, args, firstpass = 0 ):
        """
        Parse the command line arguments
        """
        all_ops = filter ( lambda x : x.isCmdLine(), self._dict.values() )

        _fail = 0
        if firstpass: 
            ops = filter ( lambda x : x._firstpass, all_ops )
        else:
            _fail = 1
            ops = filter ( lambda x : not x._firstpass, all_ops )

        return self._parseOps ( args, ops, invalid_op_fail = _fail )

    def _parseOps ( self, args, ops, 
                    invalid_op_fail = 0):
        """
        This is the function which actually parses command line 
        arguments, kind of a replacement for python getopt() which 
        sucks ass anyway
        """

        # If nothing, return happily
        if not len(ops):
            return args

        # Create the dict of short options / long options
        shorts, longs = {}, {}

        for op in ops:
            if op.shortOp():
                shorts[op.shortOp()] = op

            if op.longOp():
                longs[op.longOp()] = op

        # Helper function
        is_op = lambda x : x[0] == '-'
        is_longop = lambda x : x[:2] == '--' 

        # Ok, start looking
        # Basically, we're only distinguishing the following cases:
        #
        # -x 
        # -x foo
        # -xfoo
        # --extra foo
        # --extra=foo
        outargs = []

        i = 0
        while i < len(args):
            this = args[i]
            if i < len(args) - 1:
                next = args[i+1]
            else:
                next = None

            # Should we copy it?
            copy = 0

            if is_op ( this ):
                if is_longop(this):
                    # Long option
                    vals = string.split(this[2:], '=')
                    if len(vals) > 1:
                        # Parameter supplied
                        op, val = vals
                    else:
                        op, val = vals[0], None

                    if op in longs.keys():
                        # Our option
                        el = longs[op]
                        if el.needsParm():
                            if not val and (not next or is_op(next)):
                                raise ParseError, \
                                      'option %s requires parameter' % \
                                      ( el.longOp(), )
                            self.setOpt ( el, val or next )
                            # Eat up next
                            if not val:
                                i = i + 1
                        elif val and not el.needsParm():
                            raise ParseError, \
                              'option %s does not require parameter' % \
                              el.longOp()
                        else:
                            # just toggle it
                            self.setOpt ( el )
                    elif invalid_op_fail:
                        raise ParseError, 'unknown option "%s"' % op
                    else:
                        # Copy to args list
                        copy = 1
                else: 
                    # Short option
                    #
                    # Allow -abc option style, plus if c requires a param
                    # -abcfoo == -ab -c foo
                    this = this[1:]             # Strip '-'
                    for idx in range ( 0, len(this) ):
                        op, rest = this[idx], this[idx+1:]
                
                        if op in shorts.keys():
                            # This is a short option, one of ours
                            el = shorts[op]

                            if el.needsParm():
                                if rest:
                                    self.setOpt ( el, rest )
                                elif next:
                                    self.setOpt ( el, next )
                                    i = i + 1
                                else:
                                    raise ParseError, \
                            "option '%s' requires parameter" % el.paramCmdLine()
                            else:
                                self.setOpt ( el )
                        elif invalid_op_fail:
                            raise ParseError, "invalid option '%s'" % op
                        else:
                            # Found unknown option, for safety just copy it 
                            # to output 
                            #
                            # XXX can screw up here, if non-first pass option
                            # is followed by first pass one - so don't do that!
                            outargs.append ( '-%s%s' % (op,rest) )
                            break
            else:
                copy = 1
            
            i = i + 1
            if copy:
                outargs.append ( this )

        return outargs

    #def __del__ ( self ):
    #    if MEMDEBUG:
    #        MEMDEBUG ( 'Config %s deleted' % self._name )
    #    pass

#
# Add ability to add sub-configs to the Config class
#
class ExtendedConfig ( Config ):
    """
    Implementaion of Config class which allows adding sub-configs to 
    the config class
    """

    def __init__ ( self, *args, **kwargs ):
        "Just call the higher level init"

        apply ( Config.__init__, (self,) + args, kwargs )
        
        self._subconfigs = {}

    def addSubConfig ( self, name, conf ):
        "Set a subconfig class"

        if self._subconfigs.has_key(name):
            raise AttributeError, 'subconfig %s already exists' % name

        self._subconfigs[name] = conf

    def __getattr__ ( self, attr ):
        "Give people access to our subconfig classes"

        if attr[0] == '_':
            raise AttributeError, 'cannot access attribute %s' % attr

        if not self._subconfigs.has_key(attr):
            raise AttributeError, 'no such subconfig: %s' % attr

        return self._subconfigs[attr]

    #def __del__ ( self ):
    #    "Just debugging"
    #    MEMDEBUG ( 'ExtendedConfig: instance deleted' )

    def unload ( self ):
        "Clean up some circular references that may exist"

        for sub in self._subconfigs.values():
            sub.unload()
            
        del self._subconfigs

        for el in self._dict.values():
            el.__dict__.clear()

#
# Make a shortcut for the element class
#
el = Element
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.