git » pymisc » master » tree

[master] / configparser.py

#!/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)