#!/usr/bin/env python
"""
A simple, pythonic configuration parser.
Alberto Bertogli (albertito@blitiri.com.ar)
It parses files of the form:
------------------ 8< ------------------ 8< ------------------
key1 = value1
key2 = 3453
group1.key1 = 26.56
group1.key2 = 01.59
------------------ 8< ------------------ 8< ------------------
It knows the available keys, and can check for data types and parse
accordingly. It supports default values.
Use it like:
# Define the available keys, their types, and the default values
settings = (
('key1', str),
('key2', int),
('key3', str, 'default value'),
('group1.key1', float),
('group1.key2', float, 9283),
)
# Parse the given filename
config = Config('config', settings)
# Use the results in a python-friendly way
print config.key1
print config.group1.key2
"""
# Use new style objects everywhere
__metaclass__ = type
class ConfigError (Exception):
pass
class Config:
def __init__(self, filename = None, settings = ()):
if not filename:
return
# settings is a list of either:
# - name or (name,), which means mandatory string
# - (name, class), which means mandatory of the given class
# - (name, class, default_value), which means optional of the
# given class, with the given default
mandatory = {}
optional = {}
have_set = []
for i in settings:
if isinstance(i, str):
mandatory[i] = str
elif len(i) == 1:
mandatory[i[0]] = str
elif len(i) == 2:
mandatory[i[0]] = i[1]
elif len(i) == 3:
optional[i[0]] = (i[1], i[2])
try:
f = open(filename)
except:
desc = "Can't find config file: %s" % filename
raise ConfigError, desc
count = 0
for l in f:
count += 1
l = l.strip()
if (not l) or l.startswith('#'):
continue
if '=' in l:
name, val = l.split('=', 1)
name = name.strip()
name = name.replace('\s', '_')
val = val.strip()
if name in mandatory:
val = self._convert_to(name, val,
mandatory[name], count)
elif name in optional:
val = self._convert_to(name, val,
optional[name][0],
count)
have_set.append(name)
self._setattr(name, val, count)
for i in mandatory.keys():
if i not in have_set:
desc = "Mandatory option %s is not set" % i
raise ConfigError, desc
for (name, (cls, default)) in optional.iteritems():
if name not in have_set:
self._setattr(name, default, -1)
def _convert_to(self, name, val, cls, lineno):
try:
return cls(val)
except:
desc = "Line %d: " % lineno
desc += "Error converting %s " % name
desc += "to mandatory data type %s" % cls
raise ConfigError, desc
def _setattr(self, name, val, lineno):
if '.' in name:
parent, name = name.split('.', 1)
try:
obj = getattr(self, parent)
except AttributeError:
obj = Config()
setattr(self, parent, obj)
obj._setattr(name, val, lineno)
else:
obj = getattr(self, name, None)
if isinstance(obj, Config):
desc = "Line %d: " % lineno
desc += "Overriding a group with a value"
raise ConfigError, desc
setattr(self, name, val)