thirdparty/google_appengine/google/appengine/api/lib_config.py
changeset 3031 7678f72140e6
equal deleted inserted replaced
3030:09cae668b536 3031:7678f72140e6
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 #
       
    17 
       
    18 """A mechanism for library configuration.
       
    19 
       
    20 Whenever App Engine library code has the need for a user-configurable
       
    21 value, it should use the following protocol:
       
    22 
       
    23 1. Pick a prefix unique to the library module, e.g. 'mylib'.
       
    24 
       
    25 2. Call lib_config.register(prefix, mapping) with that prefix as
       
    26    the first argument and a dict mapping suffixes to default functions
       
    27    as the second.
       
    28 
       
    29 3. The register() function returns a config handle unique to this
       
    30    prefix.  The config handle object has attributes corresponding to
       
    31    each of the suffixes given in the mapping.  Call these functions
       
    32    (they're not really methods even though they look like methods) to
       
    33    access the user's configuration value.  If the user didn't
       
    34    configure a function, the default function from the mapping is
       
    35    called instead.
       
    36 
       
    37 4. Document the function name and its signature and semantics.
       
    38 
       
    39 Users wanting to provide configuration values should create a module
       
    40 named appengine_config.py in the top-level directory of their
       
    41 application, and define functions as documented by various App Engine
       
    42 library components in that module.  To change the configuration, edit
       
    43 the file and re-deploy the application.  (When using the SDK, no
       
    44 redeployment is required: the development server will pick up the
       
    45 changes the next time it handles a request.)
       
    46 
       
    47 Third party libraries can also use this mechanism.  For casual use,
       
    48 just calling the register() method with a unique prefix is okay.  For
       
    49 carefull libraries, however, it is recommended to instantiate a new
       
    50 LibConfigRegistry instance using a different module name.
       
    51 
       
    52 Example appengine_config.py file:
       
    53 
       
    54   from somewhere import MyMiddleWareClass
       
    55 
       
    56   def mylib_add_middleware(app):
       
    57     app = MyMiddleWareClass(app)
       
    58     return app
       
    59 
       
    60 Example library use:
       
    61 
       
    62   from google.appengine.api import lib_config
       
    63 
       
    64   config_handle = lib_config.register(
       
    65       'mylib',
       
    66       {'add_middleware': lambda app: app})
       
    67 
       
    68   def add_middleware(app):
       
    69     return config_handle.add_middleware(app)
       
    70 """
       
    71 
       
    72 
       
    73 
       
    74 import logging
       
    75 import os
       
    76 import sys
       
    77 
       
    78 
       
    79 DEFAULT_MODNAME = 'appengine_config'
       
    80 
       
    81 
       
    82 class LibConfigRegistry(object):
       
    83   """A registry for library configuration values."""
       
    84 
       
    85   def __init__(self, modname):
       
    86     """Constructor.
       
    87 
       
    88     Args:
       
    89       modname: The module name to be imported.
       
    90 
       
    91     Note: the actual import of this module is deferred until the first
       
    92     time a configuration value is requested through attribute access
       
    93     on a ConfigHandle instance.
       
    94     """
       
    95     self._modname = modname
       
    96     self._registrations = {}
       
    97     self._module = None
       
    98 
       
    99   def register(self, prefix, mapping):
       
   100     """Register a set of configuration names.
       
   101 
       
   102     Args:
       
   103       prefix: A shared prefix for the configuration names being registered.
       
   104           If the prefix doesn't end in '_', that character is appended.
       
   105       mapping: A dict mapping suffix strings to default values.
       
   106 
       
   107     Returns:
       
   108       A ConfigHandle instance.
       
   109 
       
   110     It's okay to re-register the same prefix: the mappings are merged,
       
   111     and for duplicate suffixes the most recent registration wins.
       
   112     """
       
   113     if not prefix.endswith('_'):
       
   114       prefix += '_'
       
   115     handle = self._registrations.get(prefix)
       
   116     if handle is None:
       
   117       handle = ConfigHandle(prefix, self)
       
   118       self._registrations[prefix] = handle
       
   119     handle._update_defaults(mapping)
       
   120     return handle
       
   121 
       
   122   def initialize(self):
       
   123     """Attempt to import the config module, if not already imported.
       
   124 
       
   125     This function always sets self._module to a value unequal
       
   126     to None: either the imported module (if imported successfully), or
       
   127     a dummy object() instance (if an ImportError was raised).  Other
       
   128     exceptions are *not* caught.
       
   129     """
       
   130     if self._module is not None:
       
   131       return
       
   132     try:
       
   133       __import__(self._modname)
       
   134     except ImportError, err:
       
   135       self._module = object()
       
   136     else:
       
   137       self._module = sys.modules[self._modname]
       
   138 
       
   139   def _pairs(self, prefix):
       
   140     """Generate (key, value) pairs from the config module matching prefix.
       
   141 
       
   142     Args:
       
   143       prefix: A prefix string ending in '_', e.g. 'mylib_'.
       
   144 
       
   145     Yields:
       
   146       (key, value) pairs where key is the configuration name with
       
   147       prefix removed, and value is the corresponding value.
       
   148     """
       
   149     mapping = getattr(self._module, '__dict__', None)
       
   150     if not mapping:
       
   151       return
       
   152     nskip = len(prefix)
       
   153     for key, value in mapping.iteritems():
       
   154       if key.startswith(prefix):
       
   155         yield key[nskip:], value
       
   156 
       
   157   def _dump(self):
       
   158     """Print info about all registrations to stdout."""
       
   159     self.initialize()
       
   160     if not hasattr(self._module, '__dict__'):
       
   161       print 'Module %s.py does not exist.' % self._modname
       
   162     elif not self._registrations:
       
   163       print 'No registrations for %s.py.' % self._modname
       
   164     else:
       
   165       print 'Registrations in %s.py:' % self._modname
       
   166       print '-'*40
       
   167       for prefix in sorted(self._registrations):
       
   168         self._registrations[prefix]._dump()
       
   169 
       
   170 
       
   171 class ConfigHandle(object):
       
   172   """A set of configuration for a single library module or package.
       
   173 
       
   174   Public attributes of instances of this class are configuration
       
   175   values.  Attributes are dynamically computed (in __getattr__()) and
       
   176   cached as regular instance attributes.
       
   177   """
       
   178 
       
   179   _initialized = False
       
   180 
       
   181   def __init__(self, prefix, registry):
       
   182     """Constructor.
       
   183 
       
   184     Args:
       
   185       prefix: A shared prefix for the configuration names being registered.
       
   186           It *must* end in '_'.  (This is enforced by LibConfigRegistry.)
       
   187       registry: A LibConfigRegistry instance.
       
   188     """
       
   189     assert prefix.endswith('_')
       
   190     self._prefix = prefix
       
   191     self._defaults = {}
       
   192     self._overrides = {}
       
   193     self._registry = registry
       
   194 
       
   195   def _update_defaults(self, mapping):
       
   196     """Update the default mappings.
       
   197 
       
   198     Args:
       
   199       mapping: A dict mapping suffix strings to default values.
       
   200     """
       
   201     for key, value in mapping.iteritems():
       
   202       if key.startswith('__') and key.endswith('__'):
       
   203         continue
       
   204       self._defaults[key] = value
       
   205     if self._initialized:
       
   206       self._update_configs()
       
   207 
       
   208   def _update_configs(self):
       
   209     """Update the configuration values.
       
   210 
       
   211     This clears the cached values, initializes the registry, and loads
       
   212     the configuration values from the config module.
       
   213     """
       
   214     if self._initialized:
       
   215       self._clear_cache()
       
   216     self._registry.initialize()
       
   217     for key, value in self._registry._pairs(self._prefix):
       
   218       if key not in self._defaults:
       
   219         logging.warn('Configuration "%s" not recognized', self._prefix + key)
       
   220       else:
       
   221         self._overrides[key] = value
       
   222     self._initialized = True
       
   223 
       
   224   def _clear_cache(self):
       
   225     """Clear the cached values."""
       
   226     for key in self._defaults:
       
   227       try:
       
   228         delattr(self, key)
       
   229       except AttributeError:
       
   230         pass
       
   231 
       
   232   def _dump(self):
       
   233     """Print info about this set of registrations to stdout."""
       
   234     print 'Prefix %s:' % self._prefix
       
   235     if self._overrides:
       
   236       print '  Overrides:'
       
   237       for key in sorted(self._overrides):
       
   238         print '    %s = %r' % (key, self._overrides[key])
       
   239     else:
       
   240       print '  No overrides'
       
   241     if self._defaults:
       
   242       print '  Defaults:'
       
   243       for key in sorted(self._defaults):
       
   244         print '    %s = %r' % (key, self._defaults[key])
       
   245     else:
       
   246       print '  No defaults'
       
   247     print '-'*40
       
   248 
       
   249   def __getattr__(self, suffix):
       
   250     """Dynamic attribute access.
       
   251 
       
   252     Args:
       
   253       suffix: The attribute name.
       
   254 
       
   255     Returns:
       
   256       A configuration values.
       
   257 
       
   258     Raises:
       
   259       AttributeError if the suffix is not a registered suffix.
       
   260 
       
   261     The first time an attribute is referenced, this method is invoked.
       
   262     The value returned taken either from the config module or from the
       
   263     registered default.
       
   264     """
       
   265     if not self._initialized:
       
   266       self._update_configs()
       
   267     if suffix in self._overrides:
       
   268       value = self._overrides[suffix]
       
   269     elif suffix in self._defaults:
       
   270       value = self._defaults[suffix]
       
   271     else:
       
   272       raise AttributeError(suffix)
       
   273     setattr(self, suffix, value)
       
   274     return value
       
   275 
       
   276 
       
   277 _default_registry = LibConfigRegistry(DEFAULT_MODNAME)
       
   278 
       
   279 
       
   280 def register(prefix, mapping):
       
   281   """Register a set of configurations with the default config module.
       
   282 
       
   283   Args:
       
   284     prefix: A shared prefix for the configuration names being registered.
       
   285         If the prefix doesn't end in '_', that character is appended.
       
   286     mapping: A dict mapping suffix strings to default values.
       
   287 
       
   288   Returns:
       
   289     A ConfigHandle instance.
       
   290   """
       
   291   return _default_registry.register(prefix, mapping)
       
   292 
       
   293 
       
   294 def main():
       
   295   """CGI-style request handler to dump the configuration.
       
   296 
       
   297   Put this in your app.yaml to enable (you can pick any URL):
       
   298 
       
   299   - url: /lib_config
       
   300     script: $PYTHON_LIB/google/appengine/api/lib_config.py
       
   301 
       
   302   Note: unless you are using the SDK, you must be admin.
       
   303   """
       
   304   if not os.getenv('SERVER_SOFTWARE', '').startswith('Dev'):
       
   305     from google.appengine.api import users
       
   306     if not users.is_current_user_admin():
       
   307       if users.get_current_user() is None:
       
   308         print 'Status: 302'
       
   309         print 'Location:', users.create_login_url(os.getenv('PATH_INFO', ''))
       
   310       else:
       
   311         print 'Status: 403'
       
   312         print
       
   313         print 'Forbidden'
       
   314       return
       
   315 
       
   316   print 'Content-type: text/plain'
       
   317   print
       
   318   _default_registry._dump()
       
   319 
       
   320 
       
   321 if __name__ == '__main__':
       
   322   main()