PyZine
 


Article Finder
People
Issue 2 - Revision 1  /   Published in 2002 


 
  Py Links:
Latest Issue
Issue 08
Issue 07
Issue 06
Issue 05
Issue 04
Issue 02
Issue 01
 
 
Downloads
     
  Articles:
Throughout the quarter we cover topics of interest to Python developers.

  Configuration Files Made Easy

  Jython & zxJDBC Database Programming

 
Extending Python with C: Part 2


  POOPy: Introduction to Objects in Python

  Scientific: Array Broadcasting in Numeric

 
 
 
     

Illustration by Lia Avant
Py Archive Article
Configuration Files Made Easy

Configuration Files Made Easy

- - - - - - - - - - - -

Sean Reifschneider | Originally published in Py Issue 2

print

Configuration files are something that many programs use. The complexity of configuration languages can vary greatly. On one end of the spectrum are simple “name” = “value” assignments (often found in shell scripts; for example, the files under /etc/sysconfig on a Red Hat Linux system). At the other end of the spectrum are domain-specific languages built for the application at hand (for example: fetchmail, sendmail, many routers’ configuration syntax).

Configuration files are commonly used, but there is no single standard that they conform to. The result is that there are very few tools for managing them. However, the Python programming language, because of its dynamic nature, can be of help.

In this article, I’ll discuss a way of creating a flexible configuration system that allows for complex Python-based configuration files. Because Python itself does most of the heavy lifting, the amount of code necessary to pull this off is small. How small? For a basic “name”=”value,” with all the normal Python syntax available, my example requires 2 lines of code.

BACKGROUND

Some of the most powerful configuration systems are domain-specific languages. These are languages that are designed specifically for doing one thing good. One example, which comes to mind, is fetchmail. Fetchmail is a program for retrieving e-mail from remote servers (for example, your ISP), and making it available on your local machine. A sample fetchmail configuration:

poll mail.example.com proto pop2:
user “jsmith”, with password “My^Hat”, is “John.Smith” here;

This is pretty straightforward—download mail via POP2 from mail.example.com, and deliver it to the local user John.Smith. The configuration language above is designed specifically to be easy for users to tell the program how to copy mail from a remote system.

Another example of a domain-specific language would be the sendmail.cf file format. It is a terse and powerful configuration language designed to be easy for computers to parse. Even so, back in the early days, it often took 10 to 15 minutes for the system to start up. Eric Allman, the driving force behind Sendmail, now a slightloy more complicated example:

CONFIGPARSER EXAMPLE

In a more general-purpose configuration language, for example using Python’s ConfigParser module, the fetchmail example configuration might look like:

[File “cfgparser-example.conf”]
#  section describing mail retrieval from 
#  “mail.example.com”.
[mail.example.com]
username: jsmith
password: My^Hat
protocol: pop2
localuser: John.Smith
spoolfile: /var/spool/mail/%(localuser)s

NOTE: Python version 2.2 and higher allows “.” in the section name. Previous versions only allowed alphanumeric, hyphen, and dash.

This is an example of the syntax recognized by the ConfigParser Python standard library module. This configuration syntax is taken directly from the Windows .INI files, the only trick being that you can use the %(var)s notation to construct results based on the value of other configuration directives.

While there isn’t a lot of flexibility in this configuration language, it’s fairly easy to use. You first need to set up the ConfigParser and read the configuration file:

import ConfigParser
cfg = ConfigParser.ConfigParser()
cfg.read(‘cfgparser-example.conf’)

Now, to find out what protocol to use when talking to the mail.example.com mail server:

>>> print cfg.get(‘mail.example.com’, ‘protocol’)
pop2
A slightly more complicated example:

import ConfigParser
cfg = ConfigParser.ConfigParser()
cfg.read(‘cfgparser.conf’)

for server in cfg.sections():
print ‘Settings for server “%s”:’ % server
for option in cfg.options(server):
print ‘ %s is: “%s”’ % (option, cfg.get(server, option))

The outer for loops over each section (the server name in our sample configuration file), and the inner loop prints out the values set for that server. The output is:

Settings for server “mail.example.com”:
username is: “jsmith”
localuser is: “John.Smith”
spoolfile is: “/var/spool/mail/John.Smith”
protocol is: “pop2”
password is: “My^Hat”

While simple to deal with, this is lacking some expressiveness. The above demonstrates about the most complexity you can achive with the ConfigParser A BETTER WAY?

At the beginning of the article I promised you a configuration file with a rich set of Python expressions, while also being very easy to use.

The basic idea being to get the Python interpreter to deal with the processing of the configuration file. This gives you access to the full set of syntax allowed by the Python language. For example, one thing I find I am frequently doing is writing code in my development environment, which I’d like to be able to directly deploy out to test and production environments. However, keeping track of multiple configuration files can be a pain. It would be nice to be able have the configuration tailor itself depending on the machine it’s running on:

[File “execfile-example-1.conf”]
import os
if os.uname()[1] == ‘development.example.com’:
server = ‘mail-test.example.com’
else:
server = ‘mail.example.com’

Using the execfile() Python built-in makes it easy to process such a configuration file. Set up a name-space (dictionary) with default value for server, and have Python parse the configuration file:

config = { ‘server’ : None }
execfile(‘execfile-example-1.conf’, {}, config)

The value of the server variable now depends on which machine it is run on. For example, on the development.example.com machine:

>>> print config[’server’]
mail-test.example.com

The execfile() built-in executes the Python code in the file specified in the first argument (execfile-example.conf). The next two arguments specify the global and local name-spaces, respectively. So, in this example we created a default configuration in which server was set to None. The code in the configuration file then checked what the system name was (with os.uname()[1]) and re-set the server variable in the local name-space.

GETTING FANCY

You can get a bit more fancy than just “name”=”value” mappings, with little additional code. Returning to our mail-retrieving example, you may want to set some global values, and allow the user to poll various mail servers. This can be done by using a class that controls the configuration data:

class config:
def __init__(self):
self.config = {
‘interactive’ : 0,
‘verbose’ : 0,
‘poll’ : self.poll,
            }

def poll(self, server, **kwargs):
  if self.config[’interactive’]:
  if raw_input(‘Fetch mail from server “%s”? ‘ % server) != ‘y’:
if self.config[’verbose’]:
print ‘Aborting poll at user request.’
return

if self.config[’verbose’]:
print ‘Retrieving mail from server “%s”’ % server
print ‘  args: %s’ % str(kwargs)

#  poll mail server here
def runconfig(self, filename):
execfile(filename, {}, self.config)

While the above looks more complicated than our original example, at its heart it’s the same execfile call. Most of the code is the application logic which responds to the poll() action. The class sets up two configuration values (interactive and verbose) and a function. Note that for this example we’re using the **kwargs syntax. This tells the Python interpreter to hand us the function’s keyword arguments as a dictionary. It could just as simply have been a list of the arguments expected:

if os.uname()[1] == ‘development.example.com’:
verbose = 1
interactive = 1
poll(‘mail.example.com’, 
      username=‘jsmith’, 
      password=‘My^Hat’, 
      protocol=‘pop2’, 
      localuser=‘John.Smith’)

This one sets verbose and interactive flags if the program is running on the development machine. It can be run by passing the name of the configuration file to the runconfig() method of the config object:

config().runconfig(‘execfile-example-2.conf’)

This produces the following output on the development machine:

Retrieve mail from server “mail.example.com”? [y]
Retrieving mail from server “mail.example.com”
args: {’protocol’: ‘pop2’, 
      ‘password’: ‘My^Hat’, 
      ‘localuser’: ‘John.Smith’, 
      ‘username’: ‘jsmith’}
SECURITY CONCERNS

As shown, this configuration mechanism allows for the execution of arbitrary code in the configuration file. Care must be taken in the event that the configuration file is writable by a user with privileges other than the user running the system. For example, a program running periodically from cron with root privileges should not read a configuration file that’s writable by a non-privileged user. This is not a concern exclusively with this configuration mechanism. You rarely want a root-level process directed by a user-writable configuration file. However, the power and flexibility of this mechanism makes it particularly easy to exploit. There are things that can be done if writing an application that runs with one user’s privileges, but need to read configuration files written by another user. For example, before reading the configuration file, the program can assume a lower-privileged user’s identity. With appropriate consideration of the security issues, this configuration mechanism can safely become a useful part of your programming toolbox.

CONCLUSION

I have used this mechanism successfully in a number of projects. It’s amazing how quickly you can make rather complex configuration systems. In a number of cases I’ve found that what would normally be special cases in the code could be moved off to the configuration file, and allow the ultimate flexibility.

One particular benefit is that users who are unfamiliar with Python can stick with fairly simple “name”=”value” and function(args) calls (which most people understand), while users who are familiar with Python can very easily tailor the operation of the system to their liking.

This particular article is Copyright © 2002 Sean Reifschneider. All Rights Reserved.
Sean Reifschneider


shim
shim

 Py is committed to bringing you great Python Articles.

shim
shim


Home   Subscribe   Migration FAQ   Contact PyZine   Write for PyZine   ZopeMag   opensourcexperts.com  

Reproduction of material from any of PyZine's pages without prior written permission is strictly prohibited. Copyright 2003 - 2005 PyZine Zope/Plone hosting by Nidelven IT