1
""" A Python based configuration file with hierarchical sections. """
4
class PyConfigFile(dict):
5
""" A Python based configuration file with hierarchical sections. """
7
###########################################################################
9
###########################################################################
11
def __init__(self, file_or_filename=None):
14
If 'file_or_filename' is specified it will be loaded immediately. It
18
b) a file-like object that must be open for reading
22
# A dictionary containing one namespace instance for each root of the
23
# config hierarchy (see the '_Namespace' class for more details).
25
# e.g. If the following sections have been loaded:-
36
# Then the dictionary will contain:-
38
# {'acme' : <A _Namespace instance>, 'tds' : <A _Namespace instance>}
42
if file_or_filename is not None:
43
self.load(file_or_filename)
47
###########################################################################
48
# 'PyConfigFile' interface.
49
###########################################################################
51
def load(self, file_or_filename):
52
""" Load the configuration from a file.
54
'file_or_filename' can be either:-
57
b) a file-like object that must be open for reading
61
# Get an open file to read from.
62
f = self._get_file(file_or_filename)
66
stripped = line.strip()
68
# Is this line a section header?
70
# If so then parse the preceding section (if there is one) and
71
# start collecting the body of the new section.
72
if stripped.startswith('[') and stripped.endswith(']'):
73
if section_name is not None:
74
self._parse_section(section_name, section_body)
76
section_name = stripped[1:-1]
79
# Otherwise, this is *not* a section header so add the line to the
80
# body of the current section. If there is no current section then
81
# we simply ignore it!
83
if section_name is not None:
86
# Parse the last section in the file.
87
if section_name is not None:
88
self._parse_section(section_name, section_body)
94
def save(self, file_or_filename):
95
""" Save the configuration to a file.
97
'file_or_filename' can be either:-
100
b) a file-like object that must be open for writing
104
f = self._get_file(file_or_filename, 'w')
106
for section_name, section_data in self.items():
107
self._write_section(f, section_name, section_data)
113
###########################################################################
115
###########################################################################
117
def _get_file(self, file_or_filename, mode='r'):
118
""" Return an open file object from a file or a filename.
120
The mode is only used if a filename is specified.
124
if isinstance(file_or_filename, basestring):
125
f = file(file_or_filename, mode)
132
def _get_namespace(self, section_name):
133
""" Return the namespace that represents the section. """
135
components = section_name.split('.')
136
namespace = self._namespaces.setdefault(components[0], _Namespace())
138
for component in components[1:]:
139
namespace = getattr(namespace, component)
143
def _parse_section(self, section_name, section_body):
146
In this implementation, we don't actually 'parse' anything - we just
147
execute the body of the section as Python code ;^)
151
# If this is the first time that we have come across the section then
152
# start with an empty dictionary for its contents. Otherwise, we will
153
# update its existing contents.
154
section = self.setdefault(section_name, {})
156
# Execute the Python code in the section dictionary.
158
# We use 'self._namespaces' as the globals for the code execution so
159
# that config values can refer to other config values using familiar
160
# Python syntax (see the '_Namespace' class for more details).
169
# blitzel = acme.foo.bar + acme.foo.baz
170
exec section_body in self._namespaces, section
172
# The '__builtins__' dictionary gets added to 'self._namespaces' as
173
# by the call to 'exec'. However, we want 'self._namespaces' to only
174
# contain '_Namespace' instances, so we do the cleanup here.
175
del self._namespaces['__builtins__']
177
# Get the section's corresponding node in the 'dotted' namespace and
178
# update it with the config values.
179
namespace = self._get_namespace(section_name)
180
namespace.__dict__.update(section)
184
def _write_section(self, f, section_name, section_data):
185
""" Write a section to a file. """
187
f.write('[%s]\n' % section_name)
189
for name, value in section_data.items():
190
f.write('%s = %s\n' % (name, repr(value)))
196
###########################################################################
197
# Debugging interface.
198
###########################################################################
200
def _pretty_print_namespaces(self):
201
""" Pretty print the 'dotted' namespaces. """
203
for name, value in self._namespaces.items():
204
print 'Namespace:', name
205
value.pretty_print(' ')
210
###############################################################################
212
###############################################################################
214
class _Namespace(object):
215
""" An object that represents a node in a dotted namespace.
217
We build up a dotted namespace so that config values can refer to other
218
config values using familiar Python syntax.
227
blitzel = acme.foo.bar + acme.foo.baz
231
###########################################################################
232
# 'object' interface.
233
###########################################################################
235
def __getattr__(self, name):
236
""" Return the attribute with the specified name. """
238
# This looks a little weird, but we are simply creating the next level
239
# in the namespace hierarchy 'on-demand'.
240
namespace = self.__dict__[name] = _Namespace()
244
###########################################################################
245
# Debugging interface.
246
###########################################################################
248
def pretty_print(self, indent=''):
249
""" Pretty print the namespace. """
251
for name, value in self.__dict__.items():
252
if isinstance(value, _Namespace):
253
print indent, 'Namespace:', name
254
value.pretty_print(indent + ' ')
257
print indent, name, ':', value
261
#### EOF ######################################################################