thirdparty/google_appengine/google/appengine/api/lib_config.py
changeset 3031 7678f72140e6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/api/lib_config.py	Fri Oct 23 13:54:11 2009 -0500
@@ -0,0 +1,322 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""A mechanism for library configuration.
+
+Whenever App Engine library code has the need for a user-configurable
+value, it should use the following protocol:
+
+1. Pick a prefix unique to the library module, e.g. 'mylib'.
+
+2. Call lib_config.register(prefix, mapping) with that prefix as
+   the first argument and a dict mapping suffixes to default functions
+   as the second.
+
+3. The register() function returns a config handle unique to this
+   prefix.  The config handle object has attributes corresponding to
+   each of the suffixes given in the mapping.  Call these functions
+   (they're not really methods even though they look like methods) to
+   access the user's configuration value.  If the user didn't
+   configure a function, the default function from the mapping is
+   called instead.
+
+4. Document the function name and its signature and semantics.
+
+Users wanting to provide configuration values should create a module
+named appengine_config.py in the top-level directory of their
+application, and define functions as documented by various App Engine
+library components in that module.  To change the configuration, edit
+the file and re-deploy the application.  (When using the SDK, no
+redeployment is required: the development server will pick up the
+changes the next time it handles a request.)
+
+Third party libraries can also use this mechanism.  For casual use,
+just calling the register() method with a unique prefix is okay.  For
+carefull libraries, however, it is recommended to instantiate a new
+LibConfigRegistry instance using a different module name.
+
+Example appengine_config.py file:
+
+  from somewhere import MyMiddleWareClass
+
+  def mylib_add_middleware(app):
+    app = MyMiddleWareClass(app)
+    return app
+
+Example library use:
+
+  from google.appengine.api import lib_config
+
+  config_handle = lib_config.register(
+      'mylib',
+      {'add_middleware': lambda app: app})
+
+  def add_middleware(app):
+    return config_handle.add_middleware(app)
+"""
+
+
+
+import logging
+import os
+import sys
+
+
+DEFAULT_MODNAME = 'appengine_config'
+
+
+class LibConfigRegistry(object):
+  """A registry for library configuration values."""
+
+  def __init__(self, modname):
+    """Constructor.
+
+    Args:
+      modname: The module name to be imported.
+
+    Note: the actual import of this module is deferred until the first
+    time a configuration value is requested through attribute access
+    on a ConfigHandle instance.
+    """
+    self._modname = modname
+    self._registrations = {}
+    self._module = None
+
+  def register(self, prefix, mapping):
+    """Register a set of configuration names.
+
+    Args:
+      prefix: A shared prefix for the configuration names being registered.
+          If the prefix doesn't end in '_', that character is appended.
+      mapping: A dict mapping suffix strings to default values.
+
+    Returns:
+      A ConfigHandle instance.
+
+    It's okay to re-register the same prefix: the mappings are merged,
+    and for duplicate suffixes the most recent registration wins.
+    """
+    if not prefix.endswith('_'):
+      prefix += '_'
+    handle = self._registrations.get(prefix)
+    if handle is None:
+      handle = ConfigHandle(prefix, self)
+      self._registrations[prefix] = handle
+    handle._update_defaults(mapping)
+    return handle
+
+  def initialize(self):
+    """Attempt to import the config module, if not already imported.
+
+    This function always sets self._module to a value unequal
+    to None: either the imported module (if imported successfully), or
+    a dummy object() instance (if an ImportError was raised).  Other
+    exceptions are *not* caught.
+    """
+    if self._module is not None:
+      return
+    try:
+      __import__(self._modname)
+    except ImportError, err:
+      self._module = object()
+    else:
+      self._module = sys.modules[self._modname]
+
+  def _pairs(self, prefix):
+    """Generate (key, value) pairs from the config module matching prefix.
+
+    Args:
+      prefix: A prefix string ending in '_', e.g. 'mylib_'.
+
+    Yields:
+      (key, value) pairs where key is the configuration name with
+      prefix removed, and value is the corresponding value.
+    """
+    mapping = getattr(self._module, '__dict__', None)
+    if not mapping:
+      return
+    nskip = len(prefix)
+    for key, value in mapping.iteritems():
+      if key.startswith(prefix):
+        yield key[nskip:], value
+
+  def _dump(self):
+    """Print info about all registrations to stdout."""
+    self.initialize()
+    if not hasattr(self._module, '__dict__'):
+      print 'Module %s.py does not exist.' % self._modname
+    elif not self._registrations:
+      print 'No registrations for %s.py.' % self._modname
+    else:
+      print 'Registrations in %s.py:' % self._modname
+      print '-'*40
+      for prefix in sorted(self._registrations):
+        self._registrations[prefix]._dump()
+
+
+class ConfigHandle(object):
+  """A set of configuration for a single library module or package.
+
+  Public attributes of instances of this class are configuration
+  values.  Attributes are dynamically computed (in __getattr__()) and
+  cached as regular instance attributes.
+  """
+
+  _initialized = False
+
+  def __init__(self, prefix, registry):
+    """Constructor.
+
+    Args:
+      prefix: A shared prefix for the configuration names being registered.
+          It *must* end in '_'.  (This is enforced by LibConfigRegistry.)
+      registry: A LibConfigRegistry instance.
+    """
+    assert prefix.endswith('_')
+    self._prefix = prefix
+    self._defaults = {}
+    self._overrides = {}
+    self._registry = registry
+
+  def _update_defaults(self, mapping):
+    """Update the default mappings.
+
+    Args:
+      mapping: A dict mapping suffix strings to default values.
+    """
+    for key, value in mapping.iteritems():
+      if key.startswith('__') and key.endswith('__'):
+        continue
+      self._defaults[key] = value
+    if self._initialized:
+      self._update_configs()
+
+  def _update_configs(self):
+    """Update the configuration values.
+
+    This clears the cached values, initializes the registry, and loads
+    the configuration values from the config module.
+    """
+    if self._initialized:
+      self._clear_cache()
+    self._registry.initialize()
+    for key, value in self._registry._pairs(self._prefix):
+      if key not in self._defaults:
+        logging.warn('Configuration "%s" not recognized', self._prefix + key)
+      else:
+        self._overrides[key] = value
+    self._initialized = True
+
+  def _clear_cache(self):
+    """Clear the cached values."""
+    for key in self._defaults:
+      try:
+        delattr(self, key)
+      except AttributeError:
+        pass
+
+  def _dump(self):
+    """Print info about this set of registrations to stdout."""
+    print 'Prefix %s:' % self._prefix
+    if self._overrides:
+      print '  Overrides:'
+      for key in sorted(self._overrides):
+        print '    %s = %r' % (key, self._overrides[key])
+    else:
+      print '  No overrides'
+    if self._defaults:
+      print '  Defaults:'
+      for key in sorted(self._defaults):
+        print '    %s = %r' % (key, self._defaults[key])
+    else:
+      print '  No defaults'
+    print '-'*40
+
+  def __getattr__(self, suffix):
+    """Dynamic attribute access.
+
+    Args:
+      suffix: The attribute name.
+
+    Returns:
+      A configuration values.
+
+    Raises:
+      AttributeError if the suffix is not a registered suffix.
+
+    The first time an attribute is referenced, this method is invoked.
+    The value returned taken either from the config module or from the
+    registered default.
+    """
+    if not self._initialized:
+      self._update_configs()
+    if suffix in self._overrides:
+      value = self._overrides[suffix]
+    elif suffix in self._defaults:
+      value = self._defaults[suffix]
+    else:
+      raise AttributeError(suffix)
+    setattr(self, suffix, value)
+    return value
+
+
+_default_registry = LibConfigRegistry(DEFAULT_MODNAME)
+
+
+def register(prefix, mapping):
+  """Register a set of configurations with the default config module.
+
+  Args:
+    prefix: A shared prefix for the configuration names being registered.
+        If the prefix doesn't end in '_', that character is appended.
+    mapping: A dict mapping suffix strings to default values.
+
+  Returns:
+    A ConfigHandle instance.
+  """
+  return _default_registry.register(prefix, mapping)
+
+
+def main():
+  """CGI-style request handler to dump the configuration.
+
+  Put this in your app.yaml to enable (you can pick any URL):
+
+  - url: /lib_config
+    script: $PYTHON_LIB/google/appengine/api/lib_config.py
+
+  Note: unless you are using the SDK, you must be admin.
+  """
+  if not os.getenv('SERVER_SOFTWARE', '').startswith('Dev'):
+    from google.appengine.api import users
+    if not users.is_current_user_admin():
+      if users.get_current_user() is None:
+        print 'Status: 302'
+        print 'Location:', users.create_login_url(os.getenv('PATH_INFO', ''))
+      else:
+        print 'Status: 403'
+        print
+        print 'Forbidden'
+      return
+
+  print 'Content-type: text/plain'
+  print
+  _default_registry._dump()
+
+
+if __name__ == '__main__':
+  main()