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