lightweight human-readable config?

Anthony Briggs abriggs at westnet.com.au
Thu Oct 30 07:01:33 EST 2003


At 10:22 PM +0000 29/10/03, Maxim Khesin wrote:
>I want to have a config file with my python proggie, satisfying the 
>following requirements:
>1) support key->(value, default)
>2) simple and intuitive to read and edit
>3) easyly readable into a python datastructure (a dictionary?)
>4) not requiring any heavy libraries needed (I am distributing my 
>proggie as a py2exe executable and do not want to bloat the size)

It's probably reinventing the wheel, but here's one I wrote a while 
ago. It might serve as a starting point, if nothing else. The actual 
code's quite short - it's all those pesky comments and unit tests 
that take up all the room ;)

It's also possible to have .py files imported (I forget how) from a 
py2exe program, so you can also go down the python formatted config 
file route.

Anthony


--

#!/usr/local/bin/python

# A stab at writing a config script
# We'll assume that # and ; are comment characters
# unless they're in quotes

# to run the unit tests, simply execute this script:
# config.py -v

import sys, os, string
import unittest

class config:
     """ A class to store configuration values in a text file, and
     read them out.
    
     To use this class, simply declare it:
    
         myconfig = config("config.txt")
        
     it will automatically read in all of the values from config.txt,
     and myconfig will then behave roughly like a dictionary:
    
         myconfig['name']  # corresponds to name=... in config.txt
    
     There are also the following functions:
    
         myconfig.keys()           # a list of all of the attributes
         myconfig.delete('wibble') # delete attribute wibble
         myconfig.readconfig()     # read in the config file again
         myconfig.writeconfig([filename]) # write the config file
                                          # filename is optional
     """
    
     def __init__(self, configfile):
         """Set up the class, and read in all of the values."""
         self._info={}
         self._file=configfile
         if os.access(self._file, os.F_OK) == 1:
             # we can read in the config file
             self.readconfig()
    
    
     # A couple of simple functions to make using the class easier
     # ie. you can just say configclassinstance['blah']
     def __getitem__(self, name):
         return self._info[name]
    
     def __setitem__(self, name, value):
         self._info[name] = value
    
     def keys(self):
         """return all of the config values."""
         return self._info.keys()
    
     def delete(self, item):
         """Delete a key from the config directory."""
         if item not in self._info.keys():
             raise IndexError, 'Item '+str(item)+' not found in the config.'
         else:
             del self._info[item]
    
    
     def readconfig(self):
         """Read in the config file, line by line."""
         # First, clear out the old config:
         self._info = {}
        
         # Now, open it and read in each line, calling _readconfigline 
to parse it
         file = open(self._file, 'r')
         for line in file.readlines():
             self._readconfigline(line)
         file.close()
        
         return
        
     def _readconfigline(self,theline):
         """Given a line, parse it and put it into the self.info dictionary."""
        
         theline = string.strip(theline)
         if theline=='' or theline[0]=='#' or theline[0]==';':
             return # this line is empty or a comment
        
         attribute, value = string.split(theline, '=', 1)
        
         attribute = string.strip(attribute)
         value = string.strip(value)
        
         if value[0] == '=':
             raise ValueError, "Too many equal signs in "+theline
            
         # First we check for quotes, just to be sure that
         # there aren't any nested comment chars in them
         value = string.strip(value)
         if '"' in value:
             if value[0]=='"' and string.count(value, '"') == 2:
                 value, ignored = string.split(value[1:], '"', 1)
             else: # they're playing funny buggers, and gave us
                   # something like attr=blah"blah"
                 raise ValueError, "Quotes misplaced in "+theline
        
         else:    # no quotes - cut sick!
             if '#' in value:
                 value, ignored = string.split(value, '#', 1)
                 value = string.strip(value)
             if ';' in value:
                 value, ignored = string.split(value, ';', 1)
                 value = string.strip(value)
        
         self._info[attribute] = value
    
    
     def writeconfig(self, filename=""):
         """Write out all of the values in the config to a file."""
        
         if filename=="":
             filename = self._file
            
         if os.access(self._file, os.F_OK) == 1:
             # We might want to raise an error at this point...
             raise IOError, "File "+self._file+" already exists!"
        
         file = open(filename, 'w')
         for item in self._info.keys():
             file.write(item+'="'+self._info[item]+'"\n')
         file.close()



class testItems(unittest.TestCase):
     """Test that config works correctly."""
    
     configtest = config("testconfig.txt")
    
     goodconfiglines = [  # line, attribute, value
                        ['womble=blah', 'womble', 'blah'],
                        ['womble2="blah2"', 'womble2', 'blah2'],
                       
                        ['blah2="wibble" #comment', 'blah2', 'wibble'],
                        ['blah3="wibble2" ;comment', 'blah3', 'wibble2'],
                        ['blah6="####"    # A comment', 'blah6', '####'],
                        ['blah7=";;;;"    ; A comment', 'blah7', ';;;;'],
                        ['blah10=quotes a go go!   ; #  #  ; ; ##; ;#',
                             'blah10', 'quotes a go go!'],
                            
                        ['blah5   =   "womble5"', 'blah5', 'womble5'],
                        ['blah9=womble72  ; comment!', 'blah9', 'womble72'],
                        ['blah11="  spaces should be preserved!  "', 'blah11',
                             "  spaces should be preserved!  "],

                        ["blah8=This won't break!", 'blah8', "This 
won't break!"],
                       
                       ]
    
     badconfiglines = [  # these config lines should throw a ValueError
                       'blah11="broken"quotes"',
                       'blah12="more"broken"quotes"',
                       'blah13=broken"quotes?"',
                       'strangequals=="blah"',
                       'strangeequals2="wibble"="blah"',
                      ]
    
     def testAddLines(self):
         """Test that lines can be added to the config object."""
         # First, clear out self.testconfig:
         for item in self.configtest.keys():
             self.configtest.delete(item)

         for item in self.goodconfiglines:
             self.configtest._readconfigline(item[0])
             self.assertEqual(item[2], self.configtest[item[1]])
        
     def testDelete(self):
         """Test that delete works."""
         # First, clear out self.testconfig:
         for item in self.configtest.keys():
             self.configtest.delete(item)

         # Now add and delete
         for item in self.goodconfiglines:
             self.configtest[item[1]]=item[2]
             self.failUnless(self.configtest[item[1]]==item[2])
            
             self.configtest.delete(item[1])
             self.failIf(item[2] in self.configtest.keys())
    
     def testConfigKeys(self):
         """Test that items added appear in config.keys()."""
         # First, clear out self.testconfig:
         for item in self.configtest.keys():
             self.configtest.delete(item)
         self.failUnless(self.configtest.keys()==[])

         # Now add and delete
         for item in self.goodconfiglines:
             self.configtest[item[1]]=item[2]
             self.failUnless(item[1] in self.configtest.keys())

    
     def testConfigLines(self):
         """Test that all of the types of config lines can be read 
from a file."""
         for item in self.goodconfiglines:
             #write the line to a config file
             file = open("testconfig.txt", "w")
             file.write(item[0])
             file.close()
            
             # Now check that it can be read from the file
             self.configtest.readconfig()
             self.assertEqual(item[2], self.configtest[item[1]])

     def testSanityCheck(self):
         """For each item in goodconfiglines, test that it can be 
written to a file
         and read back unchanged."""
         # First, clear out self.testconfig:
         for item in self.configtest.keys():
             self.configtest.delete(item)
        
         # Now test!
         for item in self.goodconfiglines:
             self.configtest[item[1]]=item[2]
             os.unlink("testconfig.txt")
             self.configtest.writeconfig("")
             self.configtest.readconfig()
             self.assertEqual(item[2],self.configtest[item[1]])
    
     def testBadConfigLines(self):
         """Test that dodgy config lines will throw an exception."""
         for item in self.badconfiglines:
             self.assertRaises(ValueError, 
self.configtest._readconfigline, theline=item)
    
        

if __name__ == "__main__":
     unittest.main()

-- 
----------------------------------------------------
HyPEraCtiVE? HeY, WhO aRE YoU cALliNg HypERaCtIve?!
aBRiGgS at wEStNeT.cOm.aU
----------------------------------------------------





More information about the Python-list mailing list