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 -def as_dict(config):
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) 53 return config
54 55
56 -class NamespaceSet(dict):
57 """A dict of config namespace names and handlers. 58 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. 63 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. 68 """ 69
70 - def __call__(self, config):
71 """Iterate through config and pass it to each namespace handler. 72 73 config 74 A flat dict, where keys use dots to separate 75 namespaces, and values are arbitrary. 76 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) 80 """ 81 # Separate the given config into namespaces 82 ns_confs = {} 83 for k in config: 84 if "." in k: 85 ns, name = k.split(".", 1) 86 bucket = ns_confs.setdefault(ns, {}) 87 bucket[name] = config[k] 88 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(): 94 # callable(k, v) 95 for ns, handler in self.items(): 96 exit = getattr(handler, "__exit__", None) 97 if exit: 98 callable = handler.__enter__() 99 no_exc = True 100 try: 101 try: 102 for k, v in ns_confs.get(ns, {}).items(): 103 callable(k, v) 104 except: 105 # The exceptional case is handled here 106 no_exc = False 107 if exit is None: 108 raise 109 if not exit(*sys.exc_info()): 110 raise 111 # The exception is swallowed if exit() returns true 112 finally: 113 # The normal and non-local-goto cases are handled here 114 if no_exc and exit: 115 exit(None, None, None) 116 else: 117 for k, v in ns_confs.get(ns, {}).items(): 118 handler(k, v)
119
120 - def __repr__(self):
121 return "%s.%s(%s)" % (self.__module__, self.__class__.__name__, 122 dict.__repr__(self))
123
124 - def __copy__(self):
125 newobj = self.__class__() 126 newobj.update(self) 127 return newobj
128 copy = __copy__
129 130
131 -class Config(dict):
132 """A dict-like set of configuration data, with defaults and namespaces. 133 134 May take a file, filename, or dict. 135 """ 136 137 defaults = {} 138 environments = {} 139 namespaces = NamespaceSet() 140
141 - def __init__(self, file=None, **kwargs):
142 self.reset() 143 if file is not None: 144 self.update(file) 145 if kwargs: 146 self.update(kwargs)
147
148 - def reset(self):
149 """Reset self to default values.""" 150 self.clear() 151 dict.update(self, self.defaults)
152
153 - def update(self, config):
154 """Update self from a dict, file or filename.""" 155 if isinstance(config, basestring): 156 # Filename 157 config = Parser().dict_from_file(config) 158 elif hasattr(config, 'read'): 159 # Open file object 160 config = Parser().dict_from_file(config) 161 else: 162 config = config.copy() 163 self._apply(config)
164
165 - def _apply(self, config):
166 """Update self from a dict.""" 167 which_env = config.get('environment') 168 if which_env: 169 env = self.environments[which_env] 170 for k in env: 171 if k not in config: 172 config[k] = env[k] 173 174 dict.update(self, config) 175 self.namespaces(config)
176
177 - def __setitem__(self, k, v):
178 dict.__setitem__(self, k, v) 179 self.namespaces({k: v})
180 181
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. 185 """ 186
187 - def optionxform(self, optionstr):
188 return optionstr
189
190 - def read(self, filenames):
191 if isinstance(filenames, basestring): 192 filenames = [filenames] 193 for filename in filenames: 194 # try: 195 # fp = open(filename) 196 # except IOError: 197 # continue 198 fp = open(filename) 199 try: 200 self._read(fp, filename) 201 finally: 202 fp.close()
203
204 - def as_dict(self, raw=False, vars=None):
205 """Convert an INI file to a dictionary""" 206 # Load INI file into a dict 207 result = {} 208 for section in self.sections(): 209 if section not in result: 210 result[section] = {} 211 for option in self.options(section): 212 value = self.get(section, option, raw=raw, vars=vars) 213 try: 214 value = unrepr(value) 215 except Exception: 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 222 return result
223
224 - def dict_from_file(self, file):
225 if hasattr(file, 'read'): 226 self.readfp(file) 227 else: 228 self.read(file) 229 return self.as_dict()
230 231 232 # public domain "unrepr" implementation, found on the web and then improved. 233 234
235 -class _Builder2:
236
237 - def build(self, o):
238 m = getattr(self, 'build_' + o.__class__.__name__, None) 239 if m is None: 240 raise TypeError("unrepr does not recognize %s" % 241 repr(o.__class__.__name__)) 242 return m(o)
243
244 - def astnode(self, s):
245 """Return a Python2 ast Node compiled from a string.""" 246 try: 247 import compiler 248 except ImportError: 249 # Fallback to eval when compiler package is not available, 250 # e.g. IronPython 1.0. 251 return eval(s) 252 253 p = compiler.parse("__tempvalue__ = " + s) 254 return p.getChildren()[1].getChildren()[0].getChildren()[1]
255
256 - def build_Subscript(self, o):
257 expr, flags, subs = o.getChildren() 258 expr = self.build(expr) 259 subs = self.build(subs) 260 return expr[subs]
261
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)
269
270 - def build_List(self, o):
271 return map(self.build, o.getChildren())
272
273 - def build_Const(self, o):
274 return o.value
275
276 - def build_Dict(self, o):
277 d = {} 278 i = iter(map(self.build, o.getChildren())) 279 for el in i: 280 d[el] = i.next() 281 return d
282
283 - def build_Tuple(self, o):
284 return tuple(self.build_List(o))
285
286 - def build_Name(self, o):
287 name = o.name 288 if name == 'None': 289 return None 290 if name == 'True': 291 return True 292 if name == 'False': 293 return False 294 295 # See if the Name is a package or module. If it is, import it. 296 try: 297 return modules(name) 298 except ImportError: 299 pass 300 301 # See if the Name is in builtins. 302 try: 303 return getattr(builtins, name) 304 except AttributeError: 305 pass 306 307 raise TypeError("unrepr could not resolve the name %s" % repr(name))
308
309 - def build_Add(self, o):
310 left, right = map(self.build, o.getChildren()) 311 return left + right
312
313 - def build_Mul(self, o):
314 left, right = map(self.build, o.getChildren()) 315 return left * right
316
317 - def build_Getattr(self, o):
318 parent = self.build(o.expr) 319 return getattr(parent, o.attrname)
320
321 - def build_NoneType(self, o):
322 return None
323
324 - def build_UnarySub(self, o):
325 return -self.build(o.getChildren()[0])
326
327 - def build_UnaryAdd(self, o):
328 return self.build(o.getChildren()[0])
329 330
331 -class _Builder3:
332
333 - def build(self, o):
334 m = getattr(self, 'build_' + o.__class__.__name__, None) 335 if m is None: 336 raise TypeError("unrepr does not recognize %s" % 337 repr(o.__class__.__name__)) 338 return m(o)
339
340 - def astnode(self, s):
341 """Return a Python3 ast Node compiled from a string.""" 342 try: 343 import ast 344 except ImportError: 345 # Fallback to eval when ast package is not available, 346 # e.g. IronPython 1.0. 347 return eval(s) 348 349 p = ast.parse("__tempvalue__ = " + s) 350 return p.body[0].value
351
352 - def build_Subscript(self, o):
353 return self.build(o.value)[self.build(o.slice)]
354
355 - def build_Index(self, o):
356 return self.build(o.value)
357
358 - def build_Call(self, o):
359 callee = self.build(o.func) 360 361 if o.args is None: 362 args = () 363 else: 364 args = tuple([self.build(a) for a in o.args]) 365 366 if o.starargs is None: 367 starargs = () 368 else: 369 starargs = self.build(o.starargs) 370 371 if o.kwargs is None: 372 kwargs = {} 373 else: 374 kwargs = self.build(o.kwargs) 375 376 return callee(*(args + starargs), **kwargs)
377
378 - def build_List(self, o):
379 return list(map(self.build, o.elts))
380
381 - def build_Str(self, o):
382 return o.s
383
384 - def build_Num(self, o):
385 return o.n
386
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)])
390
391 - def build_Tuple(self, o):
392 return tuple(self.build_List(o))
393
394 - def build_Name(self, o):
395 name = o.id 396 if name == 'None': 397 return None 398 if name == 'True': 399 return True 400 if name == 'False': 401 return False 402 403 # See if the Name is a package or module. If it is, import it. 404 try: 405 return modules(name) 406 except ImportError: 407 pass 408 409 # See if the Name is in builtins. 410 try: 411 import builtins 412 return getattr(builtins, name) 413 except AttributeError: 414 pass 415 416 raise TypeError("unrepr could not resolve the name %s" % repr(name))
417
418 - def build_UnaryOp(self, o):
419 op, operand = map(self.build, [o.op, o.operand]) 420 return op(operand)
421
422 - def build_BinOp(self, o):
423 left, op, right = map(self.build, [o.left, o.op, o.right]) 424 return op(left, right)
425
426 - def build_Add(self, o):
427 return _operator.add
428
429 - def build_Mult(self, o):
430 return _operator.mul
431
432 - def build_USub(self, o):
433 return _operator.neg
434
435 - def build_Attribute(self, o):
436 parent = self.build(o.value) 437 return getattr(parent, o.attr)
438
439 - def build_NoneType(self, o):
440 return None
441 442
443 -def unrepr(s):
444 """Return a Python object compiled from a string.""" 445 if not s: 446 return s 447 if sys.version_info < (3, 0): 448 b = _Builder2() 449 else: 450 b = _Builder3() 451 obj = b.astnode(s) 452 return b.build(obj)
453 454
455 -def modules(modulePath):
456 """Load a module and retrieve a reference to that module.""" 457 try: 458 mod = sys.modules[modulePath] 459 if mod is None: 460 raise KeyError() 461 except KeyError: 462 # The last [''] is important. 463 mod = __import__(modulePath, globals(), locals(), ['']) 464 return mod
465
466 -def attributes(full_attribute_name):
467 """Load a module and retrieve an attribute of that module.""" 468 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] 473 474 mod = modules(mod_path) 475 # Let an AttributeError propagate outward. 476 try: 477 attr = getattr(mod, attr_name) 478 except AttributeError: 479 raise AttributeError("'%s' object has no attribute '%s'" 480 % (mod_path, attr_name)) 481 482 # Return a reference to the attribute. 483 return attr
484