Bundled cherrypy.
[smonitor.git] / monitor / cherrypy / lib / reprconf.py
blobe18949ee1d3ccff5677454f634e9dc78209f60f9
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).
7 Namespaces
8 ----------
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.
19 """
21 try:
22 # Python 3.0+
23 from configparser import ConfigParser
24 except ImportError:
25 from ConfigParser import ConfigParser
27 try:
28 set
29 except NameError:
30 from sets import Set as set
31 import sys
33 def as_dict(config):
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)
39 return 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.
54 """
56 def __call__(self, config):
57 """Iterate through config and pass it to each namespace handler.
59 config
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)
66 """
67 # Separate the given config into namespaces
68 ns_confs = {}
69 for k in config:
70 if "." in k:
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():
80 # callable(k, v)
81 for ns, handler in self.items():
82 exit = getattr(handler, "__exit__", None)
83 if exit:
84 callable = handler.__enter__()
85 no_exc = True
86 try:
87 try:
88 for k, v in ns_confs.get(ns, {}).items():
89 callable(k, v)
90 except:
91 # The exceptional case is handled here
92 no_exc = False
93 if exit is None:
94 raise
95 if not exit(*sys.exc_info()):
96 raise
97 # The exception is swallowed if exit() returns true
98 finally:
99 # The normal and non-local-goto cases are handled here
100 if no_exc and exit:
101 exit(None, None, None)
102 else:
103 for k, v in ns_confs.get(ns, {}).items():
104 handler(k, v)
106 def __repr__(self):
107 return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
108 dict.__repr__(self))
110 def __copy__(self):
111 newobj = self.__class__()
112 newobj.update(self)
113 return newobj
114 copy = __copy__
117 class Config(dict):
118 """A dict-like set of configuration data, with defaults and namespaces.
120 May take a file, filename, or dict.
123 defaults = {}
124 environments = {}
125 namespaces = NamespaceSet()
127 def __init__(self, file=None, **kwargs):
128 self.reset()
129 if file is not None:
130 self.update(file)
131 if kwargs:
132 self.update(kwargs)
134 def reset(self):
135 """Reset self to default values."""
136 self.clear()
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):
142 # Filename
143 config = Parser().dict_from_file(config)
144 elif hasattr(config, 'read'):
145 # Open file object
146 config = Parser().dict_from_file(config)
147 else:
148 config = config.copy()
149 self._apply(config)
151 def _apply(self, config):
152 """Update self from a dict."""
153 which_env = config.get('environment')
154 if which_env:
155 env = self.environments[which_env]
156 for k in env:
157 if k not in config:
158 config[k] = env[k]
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):
174 return optionstr
176 def read(self, filenames):
177 if isinstance(filenames, basestring):
178 filenames = [filenames]
179 for filename in filenames:
180 # try:
181 # fp = open(filename)
182 # except IOError:
183 # continue
184 fp = open(filename)
185 try:
186 self._read(fp, filename)
187 finally:
188 fp.close()
190 def as_dict(self, raw=False, vars=None):
191 """Convert an INI file to a dictionary"""
192 # Load INI file into a dict
193 result = {}
194 for section in self.sections():
195 if section not in result:
196 result[section] = {}
197 for option in self.options(section):
198 value = self.get(section, option, raw, vars)
199 try:
200 value = unrepr(value)
201 except Exception, x:
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
207 return result
209 def dict_from_file(self, file):
210 if hasattr(file, 'read'):
211 self.readfp(file)
212 else:
213 self.read(file)
214 return self.as_dict()
217 # public domain "unrepr" implementation, found on the web and then improved.
219 class _Builder:
221 def build(self, o):
222 m = getattr(self, 'build_' + o.__class__.__name__, None)
223 if m is None:
224 raise TypeError("unrepr does not recognize %s" %
225 repr(o.__class__.__name__))
226 return m(o)
228 def build_Subscript(self, o):
229 expr, flags, subs = o.getChildren()
230 expr = self.build(expr)
231 subs = self.build(subs)
232 return expr[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):
246 return o.value
248 def build_Dict(self, o):
249 d = {}
250 i = iter(map(self.build, o.getChildren()))
251 for el in i:
252 d[el] = i.next()
253 return d
255 def build_Tuple(self, o):
256 return tuple(self.build_List(o))
258 def build_Name(self, o):
259 name = o.name
260 if name == 'None':
261 return None
262 if name == 'True':
263 return True
264 if name == 'False':
265 return False
267 # See if the Name is a package or module. If it is, import it.
268 try:
269 return modules(name)
270 except ImportError:
271 pass
273 # See if the Name is in builtins.
274 try:
275 import __builtin__
276 return getattr(__builtin__, name)
277 except AttributeError:
278 pass
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())
284 return left + right
286 def build_Getattr(self, o):
287 parent = self.build(o.expr)
288 return getattr(parent, o.attrname)
290 def build_NoneType(self, o):
291 return None
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])
300 def _astnode(s):
301 """Return a Python ast Node compiled from a string."""
302 try:
303 import compiler
304 except ImportError:
305 # Fallback to eval when compiler package is not available,
306 # e.g. IronPython 1.0.
307 return eval(s)
309 p = compiler.parse("__tempvalue__ = " + s)
310 return p.getChildren()[1].getChildren()[0].getChildren()[1]
313 def unrepr(s):
314 """Return a Python object compiled from a string."""
315 if not s:
316 return s
317 obj = _astnode(s)
318 return _Builder().build(obj)
321 def modules(modulePath):
322 """Load a module and retrieve a reference to that module."""
323 try:
324 mod = sys.modules[modulePath]
325 if mod is None:
326 raise KeyError()
327 except KeyError:
328 # The last [''] is important.
329 mod = __import__(modulePath, globals(), locals(), [''])
330 return mod
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.
342 try:
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.
349 return attr