OLD | NEW |
(Empty) | |
| 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 |
| 485 |
OLD | NEW |