Package cherrypy :: Package lib :: Module reprconf
[hide private]
[frames] | no frames]

Source Code for Module cherrypy.lib.reprconf

  1  """Generic configuration system using unrepr. 
  2   
  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). 
  6   
  7  Namespaces 
  8  ---------- 
  9   
 10  Configuration keys are separated into namespaces by the first "." in the key. 
 11   
 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. 
 15   
 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  """ 
 20   
 21  try: 
 22      # Python 3.0+ 
 23      from configparser import ConfigParser 
 24  except ImportError: 
 25      from ConfigParser import ConfigParser 
 26   
 27  try: 
 28      set 
 29  except NameError: 
 30      from sets import Set as set 
 31   
 32  try: 
 33      basestring 
 34  except NameError: 
 35      basestring = str 
 36   
 37  try: 
 38      # Python 3 
 39      import builtins 
 40  except ImportError: 
 41      # Python 2 
 42      import __builtin__ as builtins 
 43   
 44  import operator as _operator 
 45  import sys 
 46   
 47   
48 -def as_dict(config):
49 """Return a dict from 'config' whether it is a dict, file, or filename.""" 50 if isinstance(config, basestring): 51 config = Parser().dict_from_file(config) 52 elif hasattr(config, 'read'): 53 config = Parser().dict_from_file(config) 54 return config
55 56
57 -class NamespaceSet(dict):
58 59 """A dict of config namespace names and handlers. 60 61 Each config entry should begin with a namespace name; the corresponding 62 namespace handler will be called once for each config entry in that 63 namespace, and will be passed two arguments: the config key (with the 64 namespace removed) and the config value. 65 66 Namespace handlers may be any Python callable; they may also be 67 Python 2.5-style 'context managers', in which case their __enter__ 68 method should return a callable to be used as the handler. 69 See cherrypy.tools (the Toolbox class) for an example. 70 """ 71
72 - def __call__(self, config):
73 """Iterate through config and pass it to each namespace handler. 74 75 config 76 A flat dict, where keys use dots to separate 77 namespaces, and values are arbitrary. 78 79 The first name in each config key is used to look up the corresponding 80 namespace handler. For example, a config entry of {'tools.gzip.on': v} 81 will call the 'tools' namespace handler with the args: ('gzip.on', v) 82 """ 83 # Separate the given config into namespaces 84 ns_confs = {} 85 for k in config: 86 if "." in k: 87 ns, name = k.split(".", 1) 88 bucket = ns_confs.setdefault(ns, {}) 89 bucket[name] = config[k] 90 91 # I chose __enter__ and __exit__ so someday this could be 92 # rewritten using Python 2.5's 'with' statement: 93 # for ns, handler in self.iteritems(): 94 # with handler as callable: 95 # for k, v in ns_confs.get(ns, {}).iteritems(): 96 # callable(k, v) 97 for ns, handler in self.items(): 98 exit = getattr(handler, "__exit__", None) 99 if exit: 100 callable = handler.__enter__() 101 no_exc = True 102 try: 103 try: 104 for k, v in ns_confs.get(ns, {}).items(): 105 callable(k, v) 106 except: 107 # The exceptional case is handled here 108 no_exc = False 109 if exit is None: 110 raise 111 if not exit(*sys.exc_info()): 112 raise 113 # The exception is swallowed if exit() returns true 114 finally: 115 # The normal and non-local-goto cases are handled here 116 if no_exc and exit: 117 exit(None, None, None) 118 else: 119 for k, v in ns_confs.get(ns, {}).items(): 120 handler(k, v)
121
122 - def __repr__(self):
123 return "%s.%s(%s)" % (self.__module__, self.__class__.__name__, 124 dict.__repr__(self))
125
126 - def __copy__(self):
127 newobj = self.__class__() 128 newobj.update(self) 129 return newobj
130 copy = __copy__
131 132
133 -class Config(dict):
134 135 """A dict-like set of configuration data, with defaults and namespaces. 136 137 May take a file, filename, or dict. 138 """ 139 140 defaults = {} 141 environments = {} 142 namespaces = NamespaceSet() 143
144 - def __init__(self, file=None, **kwargs):
145 self.reset() 146 if file is not None: 147 self.update(file) 148 if kwargs: 149 self.update(kwargs)
150
151 - def reset(self):
152 """Reset self to default values.""" 153 self.clear() 154 dict.update(self, self.defaults)
155
156 - def update(self, config):
157 """Update self from a dict, file or filename.""" 158 if isinstance(config, basestring): 159 # Filename 160 config = Parser().dict_from_file(config) 161 elif hasattr(config, 'read'): 162 # Open file object 163 config = Parser().dict_from_file(config) 164 else: 165 config = config.copy() 166 self._apply(config)
167
168 - def _apply(self, config):
169 """Update self from a dict.""" 170 which_env = config.get('environment') 171 if which_env: 172 env = self.environments[which_env] 173 for k in env: 174 if k not in config: 175 config[k] = env[k] 176 177 dict.update(self, config) 178 self.namespaces(config)
179
180 - def __setitem__(self, k, v):
181 dict.__setitem__(self, k, v) 182 self.namespaces({k: v})
183 184
185 -class Parser(ConfigParser):
186 187 """Sub-class of ConfigParser that keeps the case of options and that 188 raises an exception if the file cannot be read. 189 """ 190
191 - def optionxform(self, optionstr):
192 return optionstr
193
194 - def read(self, filenames):
195 if isinstance(filenames, basestring): 196 filenames = [filenames] 197 for filename in filenames: 198 # try: 199 # fp = open(filename) 200 # except IOError: 201 # continue 202 fp = open(filename) 203 try: 204 self._read(fp, filename) 205 finally: 206 fp.close()
207
208 - def as_dict(self, raw=False, vars=None):
209 """Convert an INI file to a dictionary""" 210 # Load INI file into a dict 211 result = {} 212 for section in self.sections(): 213 if section not in result: 214 result[section] = {} 215 for option in self.options(section): 216 value = self.get(section, option, raw=raw, vars=vars) 217 try: 218 value = unrepr(value) 219 except Exception: 220 x = sys.exc_info()[1] 221 msg = ("Config error in section: %r, option: %r, " 222 "value: %r. Config values must be valid Python." % 223 (section, option, value)) 224 raise ValueError(msg, x.__class__.__name__, x.args) 225 result[section][option] = value 226 return result
227
228 - def dict_from_file(self, file):
229 if hasattr(file, 'read'): 230 self.readfp(file) 231 else: 232 self.read(file) 233 return self.as_dict()
234 235 236 # public domain "unrepr" implementation, found on the web and then improved. 237 238
239 -class _Builder2:
240
241 - def build(self, o):
242 m = getattr(self, 'build_' + o.__class__.__name__, None) 243 if m is None: 244 raise TypeError("unrepr does not recognize %s" % 245 repr(o.__class__.__name__)) 246 return m(o)
247
248 - def astnode(self, s):
249 """Return a Python2 ast Node compiled from a string.""" 250 try: 251 import compiler 252 except ImportError: 253 # Fallback to eval when compiler package is not available, 254 # e.g. IronPython 1.0. 255 return eval(s) 256 257 p = compiler.parse("__tempvalue__ = " + s) 258 return p.getChildren()[1].getChildren()[0].getChildren()[1]
259
260 - def build_Subscript(self, o):
261 expr, flags, subs = o.getChildren() 262 expr = self.build(expr) 263 subs = self.build(subs) 264 return expr[subs]
265
266 - def build_CallFunc(self, o):
267 children = o.getChildren() 268 # Build callee from first child 269 callee = self.build(children[0]) 270 # Build args and kwargs from remaining children 271 args = [] 272 kwargs = {} 273 for child in children[1:]: 274 class_name = child.__class__.__name__ 275 # None is ignored 276 if class_name == 'NoneType': 277 continue 278 # Keywords become kwargs 279 if class_name == 'Keyword': 280 kwargs.update(self.build(child)) 281 # Everything else becomes args 282 else : 283 args.append(self.build(child)) 284 return callee(*args, **kwargs)
285
286 - def build_Keyword(self, o):
287 key, value_obj = o.getChildren() 288 value = self.build(value_obj) 289 kw_dict = {key: value} 290 return kw_dict
291
292 - def build_List(self, o):
293 return map(self.build, o.getChildren())
294
295 - def build_Const(self, o):
296 return o.value
297
298 - def build_Dict(self, o):
299 d = {} 300 i = iter(map(self.build, o.getChildren())) 301 for el in i: 302 d[el] = i.next() 303 return d
304
305 - def build_Tuple(self, o):
306 return tuple(self.build_List(o))
307
308 - def build_Name(self, o):
309 name = o.name 310 if name == 'None': 311 return None 312 if name == 'True': 313 return True 314 if name == 'False': 315 return False 316 317 # See if the Name is a package or module. If it is, import it. 318 try: 319 return modules(name) 320 except ImportError: 321 pass 322 323 # See if the Name is in builtins. 324 try: 325 return getattr(builtins, name) 326 except AttributeError: 327 pass 328 329 raise TypeError("unrepr could not resolve the name %s" % repr(name))
330
331 - def build_Add(self, o):
332 left, right = map(self.build, o.getChildren()) 333 return left + right
334
335 - def build_Mul(self, o):
336 left, right = map(self.build, o.getChildren()) 337 return left * right
338
339 - def build_Getattr(self, o):
340 parent = self.build(o.expr) 341 return getattr(parent, o.attrname)
342
343 - def build_NoneType(self, o):
344 return None
345
346 - def build_UnarySub(self, o):
347 return -self.build(o.getChildren()[0])
348
349 - def build_UnaryAdd(self, o):
350 return self.build(o.getChildren()[0])
351 352
353 -class _Builder3:
354
355 - def build(self, o):
356 m = getattr(self, 'build_' + o.__class__.__name__, None) 357 if m is None: 358 raise TypeError("unrepr does not recognize %s" % 359 repr(o.__class__.__name__)) 360 return m(o)
361
362 - def astnode(self, s):
363 """Return a Python3 ast Node compiled from a string.""" 364 try: 365 import ast 366 except ImportError: 367 # Fallback to eval when ast package is not available, 368 # e.g. IronPython 1.0. 369 return eval(s) 370 371 p = ast.parse("__tempvalue__ = " + s) 372 return p.body[0].value
373
374 - def build_Subscript(self, o):
375 return self.build(o.value)[self.build(o.slice)]
376
377 - def build_Index(self, o):
378 return self.build(o.value)
379
380 - def build_Call(self, o):
381 callee = self.build(o.func) 382 383 if o.args is None: 384 args = () 385 else: 386 args = tuple([self.build(a) for a in o.args]) 387 388 if o.starargs is None: 389 starargs = () 390 else: 391 starargs = self.build(o.starargs) 392 393 if o.kwargs is None: 394 kwargs = {} 395 else: 396 kwargs = self.build(o.kwargs) 397 398 return callee(*(args + starargs), **kwargs)
399
400 - def build_List(self, o):
401 return list(map(self.build, o.elts))
402
403 - def build_Str(self, o):
404 return o.s
405
406 - def build_Num(self, o):
407 return o.n
408
409 - def build_Dict(self, o):
410 return dict([(self.build(k), self.build(v)) 411 for k, v in zip(o.keys, o.values)])
412
413 - def build_Tuple(self, o):
414 return tuple(self.build_List(o))
415
416 - def build_Name(self, o):
417 name = o.id 418 if name == 'None': 419 return None 420 if name == 'True': 421 return True 422 if name == 'False': 423 return False 424 425 # See if the Name is a package or module. If it is, import it. 426 try: 427 return modules(name) 428 except ImportError: 429 pass 430 431 # See if the Name is in builtins. 432 try: 433 import builtins 434 return getattr(builtins, name) 435 except AttributeError: 436 pass 437 438 raise TypeError("unrepr could not resolve the name %s" % repr(name))
439
440 - def build_NameConstant(self, o):
441 return o.value
442
443 - def build_UnaryOp(self, o):
444 op, operand = map(self.build, [o.op, o.operand]) 445 return op(operand)
446
447 - def build_BinOp(self, o):
448 left, op, right = map(self.build, [o.left, o.op, o.right]) 449 return op(left, right)
450
451 - def build_Add(self, o):
452 return _operator.add
453
454 - def build_Mult(self, o):
455 return _operator.mul
456
457 - def build_USub(self, o):
458 return _operator.neg
459
460 - def build_Attribute(self, o):
461 parent = self.build(o.value) 462 return getattr(parent, o.attr)
463
464 - def build_NoneType(self, o):
465 return None
466 467
468 -def unrepr(s):
469 """Return a Python object compiled from a string.""" 470 if not s: 471 return s 472 if sys.version_info < (3, 0): 473 b = _Builder2() 474 else: 475 b = _Builder3() 476 obj = b.astnode(s) 477 return b.build(obj)
478 479
480 -def modules(modulePath):
481 """Load a module and retrieve a reference to that module.""" 482 __import__(modulePath) 483 return sys.modules[modulePath]
484 485
486 -def attributes(full_attribute_name):
487 """Load a module and retrieve an attribute of that module.""" 488 489 # Parse out the path, module, and attribute 490 last_dot = full_attribute_name.rfind(".") 491 attr_name = full_attribute_name[last_dot + 1:] 492 mod_path = full_attribute_name[:last_dot] 493 494 mod = modules(mod_path) 495 # Let an AttributeError propagate outward. 496 try: 497 attr = getattr(mod, attr_name) 498 except AttributeError: 499 raise AttributeError("'%s' object has no attribute '%s'" 500 % (mod_path, attr_name)) 501 502 # Return a reference to the attribute. 503 return attr
504