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
34 """Return a dict from 'config' whether it is a dict, file, or filename."""
35 if isinstance(config
, basestring
):
36 config
= Parser().dict_from_file(config
)
37 elif hasattr(config
, 'read'):
38 config
= Parser().dict_from_file(config
)
42 class NamespaceSet(dict):
43 """A dict of config namespace names and handlers.
45 Each config entry should begin with a namespace name; the corresponding
46 namespace handler will be called once for each config entry in that
47 namespace, and will be passed two arguments: the config key (with the
48 namespace removed) and the config value.
50 Namespace handlers may be any Python callable; they may also be
51 Python 2.5-style 'context managers', in which case their __enter__
52 method should return a callable to be used as the handler.
53 See cherrypy.tools (the Toolbox class) for an example.
56 def __call__(self
, config
):
57 """Iterate through config and pass it to each namespace handler.
60 A flat dict, where keys use dots to separate
61 namespaces, and values are arbitrary.
63 The first name in each config key is used to look up the corresponding
64 namespace handler. For example, a config entry of {'tools.gzip.on': v}
65 will call the 'tools' namespace handler with the args: ('gzip.on', v)
67 # Separate the given config into namespaces
71 ns
, name
= k
.split(".", 1)
72 bucket
= ns_confs
.setdefault(ns
, {})
73 bucket
[name
] = config
[k
]
75 # I chose __enter__ and __exit__ so someday this could be
76 # rewritten using Python 2.5's 'with' statement:
77 # for ns, handler in self.iteritems():
78 # with handler as callable:
79 # for k, v in ns_confs.get(ns, {}).iteritems():
81 for ns
, handler
in self
.items():
82 exit
= getattr(handler
, "__exit__", None)
84 callable = handler
.__enter
__()
88 for k
, v
in ns_confs
.get(ns
, {}).items():
91 # The exceptional case is handled here
95 if not exit(*sys
.exc_info()):
97 # The exception is swallowed if exit() returns true
99 # The normal and non-local-goto cases are handled here
101 exit(None, None, None)
103 for k
, v
in ns_confs
.get(ns
, {}).items():
107 return "%s.%s(%s)" % (self
.__module
__, self
.__class
__.__name
__,
111 newobj
= self
.__class
__()
118 """A dict-like set of configuration data, with defaults and namespaces.
120 May take a file, filename, or dict.
125 namespaces
= NamespaceSet()
127 def __init__(self
, file=None, **kwargs
):
135 """Reset self to default values."""
137 dict.update(self
, self
.defaults
)
139 def update(self
, config
):
140 """Update self from a dict, file or filename."""
141 if isinstance(config
, basestring
):
143 config
= Parser().dict_from_file(config
)
144 elif hasattr(config
, 'read'):
146 config
= Parser().dict_from_file(config
)
148 config
= config
.copy()
151 def _apply(self
, config
):
152 """Update self from a dict."""
153 which_env
= config
.get('environment')
155 env
= self
.environments
[which_env
]
160 dict.update(self
, config
)
161 self
.namespaces(config
)
163 def __setitem__(self
, k
, v
):
164 dict.__setitem
__(self
, k
, v
)
165 self
.namespaces({k
: v
})
168 class Parser(ConfigParser
):
169 """Sub-class of ConfigParser that keeps the case of options and that
170 raises an exception if the file cannot be read.
173 def optionxform(self
, optionstr
):
176 def read(self
, filenames
):
177 if isinstance(filenames
, basestring
):
178 filenames
= [filenames
]
179 for filename
in filenames
:
181 # fp = open(filename)
186 self
._read
(fp
, filename
)
190 def as_dict(self
, raw
=False, vars=None):
191 """Convert an INI file to a dictionary"""
192 # Load INI file into a dict
194 for section
in self
.sections():
195 if section
not in result
:
197 for option
in self
.options(section
):
198 value
= self
.get(section
, option
, raw
, vars)
200 value
= unrepr(value
)
202 msg
= ("Config error in section: %r, option: %r, "
203 "value: %r. Config values must be valid Python." %
204 (section
, option
, value
))
205 raise ValueError(msg
, x
.__class
__.__name
__, x
.args
)
206 result
[section
][option
] = value
209 def dict_from_file(self
, file):
210 if hasattr(file, 'read'):
214 return self
.as_dict()
217 # public domain "unrepr" implementation, found on the web and then improved.
222 m
= getattr(self
, 'build_' + o
.__class
__.__name
__, None)
224 raise TypeError("unrepr does not recognize %s" %
225 repr(o
.__class
__.__name
__))
228 def build_Subscript(self
, o
):
229 expr
, flags
, subs
= o
.getChildren()
230 expr
= self
.build(expr
)
231 subs
= self
.build(subs
)
234 def build_CallFunc(self
, o
):
235 children
= map(self
.build
, o
.getChildren())
236 callee
= children
.pop(0)
237 kwargs
= children
.pop() or {}
238 starargs
= children
.pop() or ()
239 args
= tuple(children
) + tuple(starargs
)
240 return callee(*args
, **kwargs
)
242 def build_List(self
, o
):
243 return map(self
.build
, o
.getChildren())
245 def build_Const(self
, o
):
248 def build_Dict(self
, o
):
250 i
= iter(map(self
.build
, o
.getChildren()))
255 def build_Tuple(self
, o
):
256 return tuple(self
.build_List(o
))
258 def build_Name(self
, o
):
267 # See if the Name is a package or module. If it is, import it.
273 # See if the Name is in builtins.
276 return getattr(__builtin__
, name
)
277 except AttributeError:
280 raise TypeError("unrepr could not resolve the name %s" % repr(name
))
282 def build_Add(self
, o
):
283 left
, right
= map(self
.build
, o
.getChildren())
286 def build_Getattr(self
, o
):
287 parent
= self
.build(o
.expr
)
288 return getattr(parent
, o
.attrname
)
290 def build_NoneType(self
, o
):
293 def build_UnarySub(self
, o
):
294 return -self
.build(o
.getChildren()[0])
296 def build_UnaryAdd(self
, o
):
297 return self
.build(o
.getChildren()[0])
301 """Return a Python ast Node compiled from a string."""
305 # Fallback to eval when compiler package is not available,
306 # e.g. IronPython 1.0.
309 p
= compiler
.parse("__tempvalue__ = " + s
)
310 return p
.getChildren()[1].getChildren()[0].getChildren()[1]
314 """Return a Python object compiled from a string."""
318 return _Builder().build(obj
)
321 def modules(modulePath
):
322 """Load a module and retrieve a reference to that module."""
324 mod
= sys
.modules
[modulePath
]
328 # The last [''] is important.
329 mod
= __import__(modulePath
, globals(), locals(), [''])
332 def attributes(full_attribute_name
):
333 """Load a module and retrieve an attribute of that module."""
335 # Parse out the path, module, and attribute
336 last_dot
= full_attribute_name
.rfind(".")
337 attr_name
= full_attribute_name
[last_dot
+ 1:]
338 mod_path
= full_attribute_name
[:last_dot
]
340 mod
= modules(mod_path
)
341 # Let an AttributeError propagate outward.
343 attr
= getattr(mod
, attr_name
)
344 except AttributeError:
345 raise AttributeError("'%s' object has no attribute '%s'"
346 % (mod_path
, attr_name
))
348 # Return a reference to the attribute.