1
"""Generic configuration system using unrepr.
3
Configuration data may be supplied as a Python dictionary, as a filename,
4
or as an open file object. When you supply a filename or file, Python's
5
builtin ConfigParser is used (with some extensions).
10
Configuration keys are separated into namespaces by the first "." in the key.
12
The only key that cannot exist in a namespace is the "environment" entry.
13
This special entry 'imports' other config entries from a template stored in
14
the Config.environments dict.
16
You can define your own namespaces to be called when new config is merged
17
by adding a named handler to Config.namespaces. The name can be any string,
18
and the handler must be either a callable or a context manager.
23
from configparser import ConfigParser
25
from ConfigParser import ConfigParser
30
from sets import Set as set
42
import __builtin__ as builtins
44
import operator as _operator
48
"""Return a dict from 'config' whether it is a dict, file, or filename."""
49
if isinstance(config, basestring):
50
config = Parser().dict_from_file(config)
51
elif hasattr(config, 'read'):
52
config = Parser().dict_from_file(config)
56
class NamespaceSet(dict):
57
"""A dict of config namespace names and handlers.
59
Each config entry should begin with a namespace name; the corresponding
60
namespace handler will be called once for each config entry in that
61
namespace, and will be passed two arguments: the config key (with the
62
namespace removed) and the config value.
64
Namespace handlers may be any Python callable; they may also be
65
Python 2.5-style 'context managers', in which case their __enter__
66
method should return a callable to be used as the handler.
67
See cherrypy.tools (the Toolbox class) for an example.
70
def __call__(self, config):
71
"""Iterate through config and pass it to each namespace handler.
74
A flat dict, where keys use dots to separate
75
namespaces, and values are arbitrary.
77
The first name in each config key is used to look up the corresponding
78
namespace handler. For example, a config entry of {'tools.gzip.on': v}
79
will call the 'tools' namespace handler with the args: ('gzip.on', v)
81
# Separate the given config into namespaces
85
ns, name = k.split(".", 1)
86
bucket = ns_confs.setdefault(ns, {})
87
bucket[name] = config[k]
89
# I chose __enter__ and __exit__ so someday this could be
90
# rewritten using Python 2.5's 'with' statement:
91
# for ns, handler in self.iteritems():
92
# with handler as callable:
93
# for k, v in ns_confs.get(ns, {}).iteritems():
95
for ns, handler in self.items():
96
exit = getattr(handler, "__exit__", None)
98
callable = handler.__enter__()
102
for k, v in ns_confs.get(ns, {}).items():
105
# The exceptional case is handled here
109
if not exit(*sys.exc_info()):
111
# The exception is swallowed if exit() returns true
113
# The normal and non-local-goto cases are handled here
115
exit(None, None, None)
117
for k, v in ns_confs.get(ns, {}).items():
121
return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
125
newobj = self.__class__()
132
"""A dict-like set of configuration data, with defaults and namespaces.
134
May take a file, filename, or dict.
139
namespaces = NamespaceSet()
141
def __init__(self, file=None, **kwargs):
149
"""Reset self to default values."""
151
dict.update(self, self.defaults)
153
def update(self, config):
154
"""Update self from a dict, file or filename."""
155
if isinstance(config, basestring):
157
config = Parser().dict_from_file(config)
158
elif hasattr(config, 'read'):
160
config = Parser().dict_from_file(config)
162
config = config.copy()
165
def _apply(self, config):
166
"""Update self from a dict."""
167
which_env = config.get('environment')
169
env = self.environments[which_env]
174
dict.update(self, config)
175
self.namespaces(config)
177
def __setitem__(self, k, v):
178
dict.__setitem__(self, k, v)
179
self.namespaces({k: v})
182
class Parser(ConfigParser):
183
"""Sub-class of ConfigParser that keeps the case of options and that
184
raises an exception if the file cannot be read.
187
def optionxform(self, optionstr):
190
def read(self, filenames):
191
if isinstance(filenames, basestring):
192
filenames = [filenames]
193
for filename in filenames:
195
# fp = open(filename)
200
self._read(fp, filename)
204
def as_dict(self, raw=False, vars=None):
205
"""Convert an INI file to a dictionary"""
206
# Load INI file into a dict
208
for section in self.sections():
209
if section not in result:
211
for option in self.options(section):
212
value = self.get(section, option, raw=raw, vars=vars)
214
value = unrepr(value)
216
x = sys.exc_info()[1]
217
msg = ("Config error in section: %r, option: %r, "
218
"value: %r. Config values must be valid Python." %
219
(section, option, value))
220
raise ValueError(msg, x.__class__.__name__, x.args)
221
result[section][option] = value
224
def dict_from_file(self, file):
225
if hasattr(file, 'read'):
229
return self.as_dict()
232
# public domain "unrepr" implementation, found on the web and then improved.
238
m = getattr(self, 'build_' + o.__class__.__name__, None)
240
raise TypeError("unrepr does not recognize %s" %
241
repr(o.__class__.__name__))
244
def astnode(self, s):
245
"""Return a Python2 ast Node compiled from a string."""
249
# Fallback to eval when compiler package is not available,
250
# e.g. IronPython 1.0.
253
p = compiler.parse("__tempvalue__ = " + s)
254
return p.getChildren()[1].getChildren()[0].getChildren()[1]
256
def build_Subscript(self, o):
257
expr, flags, subs = o.getChildren()
258
expr = self.build(expr)
259
subs = self.build(subs)
262
def build_CallFunc(self, o):
263
children = map(self.build, o.getChildren())
264
callee = children.pop(0)
265
kwargs = children.pop() or {}
266
starargs = children.pop() or ()
267
args = tuple(children) + tuple(starargs)
268
return callee(*args, **kwargs)
270
def build_List(self, o):
271
return map(self.build, o.getChildren())
273
def build_Const(self, o):
276
def build_Dict(self, o):
278
i = iter(map(self.build, o.getChildren()))
283
def build_Tuple(self, o):
284
return tuple(self.build_List(o))
286
def build_Name(self, o):
295
# See if the Name is a package or module. If it is, import it.
301
# See if the Name is in builtins.
303
return getattr(builtins, name)
304
except AttributeError:
307
raise TypeError("unrepr could not resolve the name %s" % repr(name))
309
def build_Add(self, o):
310
left, right = map(self.build, o.getChildren())
313
def build_Mul(self, o):
314
left, right = map(self.build, o.getChildren())
317
def build_Getattr(self, o):
318
parent = self.build(o.expr)
319
return getattr(parent, o.attrname)
321
def build_NoneType(self, o):
324
def build_UnarySub(self, o):
325
return -self.build(o.getChildren()[0])
327
def build_UnaryAdd(self, o):
328
return self.build(o.getChildren()[0])
334
m = getattr(self, 'build_' + o.__class__.__name__, None)
336
raise TypeError("unrepr does not recognize %s" %
337
repr(o.__class__.__name__))
340
def astnode(self, s):
341
"""Return a Python3 ast Node compiled from a string."""
345
# Fallback to eval when ast package is not available,
346
# e.g. IronPython 1.0.
349
p = ast.parse("__tempvalue__ = " + s)
350
return p.body[0].value
352
def build_Subscript(self, o):
353
return self.build(o.value)[self.build(o.slice)]
355
def build_Index(self, o):
356
return self.build(o.value)
358
def build_Call(self, o):
359
callee = self.build(o.func)
364
args = tuple([self.build(a) for a in o.args])
366
if o.starargs is None:
369
starargs = self.build(o.starargs)
374
kwargs = self.build(o.kwargs)
376
return callee(*(args + starargs), **kwargs)
378
def build_List(self, o):
379
return list(map(self.build, o.elts))
381
def build_Str(self, o):
384
def build_Num(self, o):
387
def build_Dict(self, o):
388
return dict([(self.build(k), self.build(v))
389
for k, v in zip(o.keys, o.values)])
391
def build_Tuple(self, o):
392
return tuple(self.build_List(o))
394
def build_Name(self, o):
403
# See if the Name is a package or module. If it is, import it.
409
# See if the Name is in builtins.
412
return getattr(builtins, name)
413
except AttributeError:
416
raise TypeError("unrepr could not resolve the name %s" % repr(name))
418
def build_UnaryOp(self, o):
419
op, operand = map(self.build, [o.op, o.operand])
422
def build_BinOp(self, o):
423
left, op, right = map(self.build, [o.left, o.op, o.right])
424
return op(left, right)
426
def build_Add(self, o):
429
def build_Mult(self, o):
432
def build_USub(self, o):
435
def build_Attribute(self, o):
436
parent = self.build(o.value)
437
return getattr(parent, o.attr)
439
def build_NoneType(self, o):
444
"""Return a Python object compiled from a string."""
447
if sys.version_info < (3, 0):
455
def modules(modulePath):
456
"""Load a module and retrieve a reference to that module."""
458
mod = sys.modules[modulePath]
462
# The last [''] is important.
463
mod = __import__(modulePath, globals(), locals(), [''])
466
def attributes(full_attribute_name):
467
"""Load a module and retrieve an attribute of that module."""
469
# Parse out the path, module, and attribute
470
last_dot = full_attribute_name.rfind(".")
471
attr_name = full_attribute_name[last_dot + 1:]
472
mod_path = full_attribute_name[:last_dot]
474
mod = modules(mod_path)
475
# Let an AttributeError propagate outward.
477
attr = getattr(mod, attr_name)
478
except AttributeError:
479
raise AttributeError("'%s' object has no attribute '%s'"
480
% (mod_path, attr_name))
482
# Return a reference to the attribute.