Python and config files routine


Few days ago I wrote a helper class to deal with 'config' files. I used Python's power to seal config files handling routine to generic, type-independend, easy-to-understand way.

What configuration files look like?
The configuration file consists of sections, led by a [section] header and followed by name: value entries, with continuations in the style of RFC 822 (see section 3.1.1, “LONG HEADER FIELDS”); name=value is also accepted [1].
For example:

1
2
3
4
5
[Options]
name=Colorpicker
version=1.2
size=30
use=True

First, let's use Python to create a configuration described above and save it in "~/test.cfg":

1
2
3
4
5
6
7
8
9
10
11
12
import ConfigParser

cp = ConfigParser.ConfigParser()

cp.add_section('Options')
cp.set('Options','name', 'Colorpicker')
cp.set('Options','version', 1.2)
cp.set('Options','size', 30)
cp.set('Options','use', True)

with open('~/tst.cfg','w') as file:
    cp.write(file)

The test.cfg file should appear in $HOME directory. Ascertain that the contents of the file are the same as in example described above. Now, let's read and parse the configuration file:

1
2
3
4
5
6
7
cp = ConfigParser.ConfigParser()
cp.read('~/tst.cfg')

name = cp.get('Options', 'name')
version = cp.getfloat('Options','version')
size = cp.getint('Options','size')
use = cp.getboolean('Options','use')

Now, the variables' values correspond to values in file. Notice, that there are four different methods that treat a variable's value as string, float, int or bool.

Imagine, that there are dozens parameters to be saved. There is pretty much code to write for loading them back. Is there a way to generalize the configuration save/load routine and decrease the amount of code? Of course there is, if we're speaking of Python.

The trick is that Python can easily determine the type of a variable, either it's a float, int, bool or string. Another trick is to initialize variables to their default values, so that variables' types could be determined.
Consider a configuration class SmartConfig that can automatically save and load it's attributes to or from a section in config file. First, let's construct a skeleton of the class:

1
2
3
4
5
6
7
8
9
10
11
class SmartConfig:
   def __init__(self, section, file):
      self._cfg = ConfigParser.ConfigParser()
      self._file = file        # configuration file
      self._section = section  # section's name in configuration file

   def save(self):
      pass

   def load(self):
      pass

Pay attention to the file parameter in constructor. If there is a single config file in a program, then file = PATH could be used (PATH is a constant or a variable).
Let's build a derived class with configuration parameters:

1
2
3
4
5
6
7
class OurConfig(SmartConfig):
   def __init__(self):
      super(OurConfig, self).__init__(self.__class__.__name__, '~/test.cfg')
      self.name = 'Colorpicker'
      self.version = 1.2
      self.size = 30
      self.use = True

Notice, that SmartConfig.__init__(...) is called. The section parameter is identical to derived class's name (OurConfig). All attributes were initialized and their types were considered by Python.
Next, a mechanism to save and load a class attributes to/from a config file should be written.

Note: Some parts of code (e.g. exceptions handling) were not included in snippets.

The save() method code is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def save(self):
   self._cfg.read(self._file) #read configuration file

   if not self._cfg.has_section(self._section): # check if section exists
      self._cfg.add_section(self._section)

   # for all attributes of SmartConfig instance
   for attr, val in self.__dict__.items(): 

      # not "required by SmartConfig class" attribute
      if not attr.startswith('_'):         
         self._cfg.set(self._section, attr, val)

   with open(self._file, 'wb') as file: # save config
      self._cfg.write(file)

The "trick described above is in line #7. All attributes that don't start with underscore are saved by self._cfg.
What about loading the file?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def load(self):
   self._cfg.read(self._file)

   for attr, val in self.__dict__.items(): # iterate throught all attributes
      if not attr.startswith('_'):         # private atributes
         if self._cfg.has_option(self._section, attr): # attribute exists

            # select proper method to parse "option's" value
            if type(val) == int: cfg_get = self._cfg.getint
            elif type(val) == float: cfg_get = self._cfg.getfloat
            elif type(val) == bool: cfg_get = self._cfg.getboolean
            else: cfg_get = self._cfg.get

            self.__setattr__(attr, cfg_get(self._section, attr)) 

Remember, that all class attributes were initialized in OurClass's constructor? The method iterates through the attributes, looks for each attribute in loaded configuration, and automatically calls appropriate ConfigParser's method for int, float, bool or str (for everything else).

I hope you've catched some new ideas from this post.
Have a nice coding!

References
1.http://docs.python.org/library/configparser.html
Comments


Leave a comment

Introduce yourself:
Comment: BB-codes
Captcha:
captcha