"""Configuration system for CherryPy. Configuration in CherryPy is implemented via dictionaries. Keys are strings which name the mapped value, which may be of any type. Architecture ------------ CherryPy Requests are part of an Application, which runs in a global context, and configuration data may apply to any of those three scopes: Global: configuration entries which apply everywhere are stored in cherrypy.config. Application: entries which apply to each mounted application are stored on the Application object itself, as 'app.config'. This is a two-level dict where each key is a path, or "relative URL" (for example, "/" or "/path/to/my/page"), and each value is a config dict. Usually, this data is provided in the call to cherrypy.tree.mount(root(), config=conf), although you may also use app.merge(conf). Request: each Request object possesses a single 'Request.config' dict. Early in the request process, this dict is populated by merging global config entries, Application entries (whose path equals or is a parent of Request.path_info), and any config acquired while looking up the page handler (see next). Declaration ----------- Configuration data may be supplied as a Python dictionary, as a filename, or as an open file object. When you supply a filename or file, CherryPy uses Python's builtin ConfigParser; you declare Application config by writing each path as a section header: [/path/to/my/page] request.stream = True To declare global configuration entries, place them in a [global] section. You may also declare config entries directly on the classes and methods (page handlers) that make up your CherryPy application via the '_cp_config' attribute. For example: class Demo: _cp_config = {'tools.gzip.on': True} def index(self): return "Hello world" index.exposed = True index._cp_config = {'request.show_tracebacks': False} Namespaces ---------- Configuration keys are separated into namespaces by the first "." in the key. Current namespaces: engine: Controls the 'application engine', including autoreload. These can only be declared in the global config. hooks: Declares additional request-processing functions. log: Configures the logging for each application. These can only be declared in the global or / config. request: Adds attributes to each Request. response: Adds attributes to each Response. server: Controls the default HTTP server via cherrypy.server. These can only be declared in the global config. tools: Runs and configures additional request-processing packages. The only key that does not exist in a namespace is the "environment" entry. This special entry 'imports' other config entries from a template stored in cherrypy._cpconfig.environments[environment]. It only applies to the global config, and only when you use cherrypy.config.update. You can define your own namespaces to be called at the Global, Application, or Request level, by adding a named handler to cherrypy.config.namespaces, app.namespaces, or cherrypy.engine.request_class.namespaces. The name can be any string, and the handler must be either a callable or a context manager. """ import ConfigParser import sys from warnings import warn import cherrypy environments = { "staging": { 'engine.autoreload_on': False, 'tools.log_headers.on': False, 'request.show_tracebacks': False, }, "production": { 'engine.autoreload_on': False, 'tools.log_headers.on': False, 'request.show_tracebacks': False, 'log.screen': False, }, "test_suite": { 'engine.autoreload_on': False, 'tools.log_headers.on': False, 'request.show_tracebacks': True, 'log.screen': False, }, } def as_dict(config): """Return a dict from 'config' whether it is a dict, file, or filename.""" if isinstance(config, basestring): config = _Parser().dict_from_file(config) elif hasattr(config, 'read'): config = _Parser().dict_from_file(config) return config def merge(base, other): """Merge one app config (from a dict, file, or filename) into another. If the given config is a filename, it will be appended to cherrypy.engine.reload_files and monitored for changes. """ if isinstance(other, basestring): if other not in cherrypy.engine.reload_files: cherrypy.engine.reload_files.append(other) # Load other into base for section, value_map in as_dict(other).iteritems(): base.setdefault(section, {}).update(value_map) def _call_namespaces(config, namespaces): """Iterate through config and pass it to each namespace. 'config' should be a flat dict, where keys use dots to separate namespaces, and values are arbitrary. 'namespaces' should be a dict whose keys are strings and whose values are namespace handlers. The first name in each config key is used to look up the corresponding namespace handler. For example, a config entry of {'tools.gzip.on': v} will call the 'tools' namespace handler with the args: ('gzip.on', v) Each handler may be a bare callable, or it may be a context manager with __enter__ and __exit__ methods, in which case the __enter__ method should return the callable. """ # Separate the given config into namespaces ns_confs = {} for k in config: if "." in k: ns, name = k.split(".", 1) bucket = ns_confs.setdefault(ns, {}) bucket[name] = config[k] # I chose __enter__ and __exit__ so someday this could be # rewritten using Python 2.5's 'with' statement: # for ns, handler in namespaces.iteritems(): # with handler as callable: # for k, v in ns_confs.get(ns, {}).iteritems(): # callable(k, v) for ns, handler in namespaces.iteritems(): exit = getattr(handler, "__exit__", None) if exit: callable = handler.__enter__() no_exc = True try: try: for k, v in ns_confs.get(ns, {}).iteritems(): callable(k, v) except: # The exceptional case is handled here no_exc = False if exit is None: raise if not exit(*sys.exc_info()): raise # The exception is swallowed if exit() returns true finally: # The normal and non-local-goto cases are handled here if no_exc and exit: exit(None, None, None) else: for k, v in ns_confs.get(ns, {}).iteritems(): handler(k, v) class Config(dict): """The 'global' configuration data for the entire CherryPy process.""" defaults = { 'tools.log_tracebacks.on': True, 'tools.log_headers.on': True, 'tools.trailing_slash.on': True, } namespaces = {"server": lambda k, v: setattr(cherrypy.server, k, v), "engine": lambda k, v: setattr(cherrypy.engine, k, v), "log": lambda k, v: setattr(cherrypy.log, k, v), } def __init__(self): self.reset() def reset(self): """Reset self to default values.""" self.clear() dict.update(self, self.defaults) def update(self, config): """Update self from a dict, file or filename.""" if isinstance(config, basestring): # Filename if config not in cherrypy.engine.reload_files: cherrypy.engine.reload_files.append(config) config = _Parser().dict_from_file(config) elif hasattr(config, 'read'): # Open file object config = _Parser().dict_from_file(config) else: config = config.copy() if isinstance(config.get("global", None), dict): config = config["global"] if 'environment' in config: env = environments[config['environment']] for k in env: if k not in config: config[k] = env[k] if 'tools.staticdir.dir' in config: config['tools.staticdir.section'] = "global" dict.update(self, config) _call_namespaces(config, self.namespaces) def __setitem__(self, k, v): dict.__setitem__(self, k, v) _call_namespaces({k: v}, self.namespaces) obsolete = { 'server.default_content_type': 'tools.response_headers.headers', 'log_access_file': 'log.access_file', 'log_config_options': None, 'log_file': 'log.error_file', 'log_file_not_found': None, 'log_request_headers': 'tools.log_headers.on', 'log_to_screen': 'log.screen', 'show_tracebacks': 'request.show_tracebacks', 'throw_errors': 'request.throw_errors', 'profiler.on': 'cherrypy.tree.mount(profiler.make_app(cherrypy.Application(Root())))', } deprecated = {} def check_compatibility(config): """Process config and warn on each obsolete or deprecated entry.""" for section, map in as_dict(config).iteritems(): if isinstance(map, dict): for k, v in map.iteritems(): if k in obsolete: warn("%r is obsolete. Use %r instead." % (k, v)) elif k in deprecated: warn("%r is deprecated. Use %r instead." % (k, v)) else: if section in obsolete: warn("%r is obsolete. Use %r instead." % (section, map)) elif section in deprecated: warn("%r is deprecated. Use %r instead." % (section, map)) class _Parser(ConfigParser.ConfigParser): """Sub-class of ConfigParser that keeps the case of options and that raises an exception if the file cannot be read. """ def optionxform(self, optionstr): return optionstr def read(self, filenames): if isinstance(filenames, basestring): filenames = [filenames] for filename in filenames: # try: # fp = open(filename) # except IOError: # continue fp = open(filename) try: self._read(fp, filename) finally: fp.close() def as_dict(self, raw=False, vars=None): """Convert an INI file to a dictionary""" # Load INI file into a dict from cherrypy.lib import unrepr result = {} for section in self.sections(): if section not in result: result[section] = {} for option in self.options(section): value = self.get(section, option, raw, vars) try: value = unrepr(value) except Exception, x: msg = ("Config error in section: %s, option: %s, value: %s" % (repr(section), repr(option), repr(value))) raise ValueError(msg, x.__class__.__name__, x.args) result[section][option] = value return result def dict_from_file(self, file): if hasattr(file, 'read'): self.readfp(file) else: self.read(file) return self.as_dict() del ConfigParser