1.9.30 sync.
[gae.git] / python / google / appengine / api / lib_config.py
blob5972f58f00e0750f688a41559af9a2636f8ba5f4
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
21 """A mechanism for library configuration.
23 Whenever App Engine library code has the need for a user-configurable
24 value, it should use the following protocol:
26 1. Pick a prefix unique to the library module, e.g. 'mylib'.
28 2. Call lib_config.register(prefix, mapping) with that prefix as
29 the first argument and a dict mapping suffixes to default functions
30 as the second.
32 3. The register() function returns a config handle unique to this
33 prefix. The config handle object has attributes corresponding to
34 each of the suffixes given in the mapping. Call these functions
35 (they're not really methods even though they look like methods) to
36 access the user's configuration value. If the user didn't
37 configure a function, the default function from the mapping is
38 called instead.
40 4. Document the function name and its signature and semantics.
42 Users wanting to provide configuration values should create a module
43 named appengine_config.py in the top-level directory of their
44 application, and define functions as documented by various App Engine
45 library components in that module. To change the configuration, edit
46 the file and re-deploy the application. (When using the SDK, no
47 redeployment is required: the development server will pick up the
48 changes the next time it handles a request.)
50 Third party libraries can also use this mechanism. For casual use,
51 just calling the register() method with a unique prefix is okay. For
52 carefull libraries, however, it is recommended to instantiate a new
53 LibConfigRegistry instance using a different module name.
55 Example appengine_config.py file:
57 from somewhere import MyMiddleWareClass
59 def mylib_add_middleware(app):
60 app = MyMiddleWareClass(app)
61 return app
63 Example library use:
65 from google.appengine.api import lib_config
67 config_handle = lib_config.register(
68 'mylib',
69 {'add_middleware': lambda app: app})
71 def add_middleware(app):
72 return config_handle.add_middleware(app)
73 """
77 __all__ = ['DEFAULT_MODNAME',
78 'LibConfigRegistry',
79 'ConfigHandle',
80 'register',
81 'main',
85 import logging
86 import os
87 import sys
88 import threading
91 DEFAULT_MODNAME = 'appengine_config'
97 class LibConfigRegistry(object):
98 """A registry for library configuration values."""
100 def __init__(self, modname):
101 """Constructor.
103 Args:
104 modname: The module name to be imported.
106 Note: the actual import of this module is deferred until the first
107 time a configuration value is requested through attribute access
108 on a ConfigHandle instance.
110 self._modname = modname
111 self._registrations = {}
112 self._module = None
113 self._lock = threading.RLock()
115 def register(self, prefix, mapping):
116 """Register a set of configuration names.
118 Args:
119 prefix: A shared prefix for the configuration names being registered.
120 If the prefix doesn't end in '_', that character is appended.
121 mapping: A dict mapping suffix strings to default values.
123 Returns:
124 A ConfigHandle instance.
126 It's okay to re-register the same prefix: the mappings are merged,
127 and for duplicate suffixes the most recent registration wins.
129 if not prefix.endswith('_'):
130 prefix += '_'
131 self._lock.acquire()
132 try:
133 handle = self._registrations.get(prefix)
134 if handle is None:
135 handle = ConfigHandle(prefix, self)
136 self._registrations[prefix] = handle
137 finally:
138 self._lock.release()
139 handle._update_defaults(mapping)
140 return handle
142 def initialize(self, import_func=__import__):
143 """Attempt to import the config module, if not already imported.
145 This function always sets self._module to a value unequal
146 to None: either the imported module (if imported successfully), or
147 a dummy object() instance (if an ImportError was raised). Other
148 exceptions are *not* caught.
150 When a dummy instance is used, it is also put in sys.modules.
151 This allows us to detect when sys.modules was changed (as
152 dev_appserver.py does when it notices source code changes) and
153 re-try the __import__ in that case, while skipping it (for speed)
154 if nothing has changed.
156 Args:
157 import_func: Used for dependency injection.
159 self._lock.acquire()
160 try:
161 if (self._module is not None and
162 self._module is sys.modules.get(self._modname)):
163 return
164 try:
165 import_func(self._modname)
166 except ImportError, err:
167 if str(err) != 'No module named %s' % self._modname:
169 raise
170 self._module = object()
171 sys.modules[self._modname] = self._module
172 else:
173 self._module = sys.modules[self._modname]
174 finally:
175 self._lock.release()
177 def reset(self):
178 """Drops the imported config module.
180 If the config module has not been imported then this is a no-op.
182 self._lock.acquire()
183 try:
184 if self._module is None:
186 return
188 self._module = None
189 handles = self._registrations.values()
190 finally:
191 self._lock.release()
192 for handle in handles:
193 handle._clear_cache()
195 def _pairs(self, prefix):
196 """Generate (key, value) pairs from the config module matching prefix.
198 Args:
199 prefix: A prefix string ending in '_', e.g. 'mylib_'.
201 Yields:
202 (key, value) pairs where key is the configuration name with
203 prefix removed, and value is the corresponding value.
205 self._lock.acquire()
206 try:
207 mapping = getattr(self._module, '__dict__', None)
208 if not mapping:
209 return
210 items = mapping.items()
211 finally:
212 self._lock.release()
213 nskip = len(prefix)
214 for key, value in items:
215 if key.startswith(prefix):
216 yield key[nskip:], value
218 def _dump(self):
219 """Print info about all registrations to stdout."""
220 self.initialize()
221 handles = []
222 self._lock.acquire()
223 try:
224 if not hasattr(self._module, '__dict__'):
225 print 'Module %s.py does not exist.' % self._modname
226 elif not self._registrations:
227 print 'No registrations for %s.py.' % self._modname
228 else:
229 print 'Registrations in %s.py:' % self._modname
230 print '-'*40
231 handles = self._registrations.items()
232 finally:
233 self._lock.release()
234 for _, handle in sorted(handles):
235 handle._dump()
238 class ConfigHandle(object):
239 """A set of configuration for a single library module or package.
241 Public attributes of instances of this class are configuration
242 values. Attributes are dynamically computed (in __getattr__()) and
243 cached as regular instance attributes.
246 _initialized = False
248 def __init__(self, prefix, registry):
249 """Constructor.
251 Args:
252 prefix: A shared prefix for the configuration names being registered.
253 It *must* end in '_'. (This is enforced by LibConfigRegistry.)
254 registry: A LibConfigRegistry instance.
256 assert prefix.endswith('_')
257 self._prefix = prefix
258 self._defaults = {}
259 self._overrides = {}
260 self._registry = registry
261 self._lock = threading.RLock()
263 def _update_defaults(self, mapping):
264 """Update the default mappings.
266 Args:
267 mapping: A dict mapping suffix strings to default values.
269 self._lock.acquire()
270 try:
271 for key, value in mapping.iteritems():
272 if key.startswith('__') and key.endswith('__'):
273 continue
274 self._defaults[key] = value
275 if self._initialized:
276 self._update_configs()
277 finally:
278 self._lock.release()
280 def _update_configs(self):
281 """Update the configuration values.
283 This clears the cached values, initializes the registry, and loads
284 the configuration values from the config module.
286 self._lock.acquire()
287 try:
288 if self._initialized:
289 self._clear_cache()
290 self._registry.initialize()
291 for key, value in self._registry._pairs(self._prefix):
292 if key not in self._defaults:
293 logging.warn('Configuration "%s" not recognized', self._prefix + key)
294 else:
295 self._overrides[key] = value
296 self._initialized = True
297 finally:
298 self._lock.release()
300 def _clear_cache(self):
301 """Clear the cached values."""
302 self._lock.acquire()
303 try:
304 self._initialized = False
305 for key in self._defaults:
306 self._overrides.pop(key, None)
307 try:
308 delattr(self, key)
309 except AttributeError:
310 pass
311 finally:
312 self._lock.release()
314 def _dump(self):
315 """Print info about this set of registrations to stdout."""
316 self._lock.acquire()
317 try:
318 print 'Prefix %s:' % self._prefix
319 if self._overrides:
320 print ' Overrides:'
321 for key in sorted(self._overrides):
322 print ' %s = %r' % (key, self._overrides[key])
323 else:
324 print ' No overrides'
325 if self._defaults:
326 print ' Defaults:'
327 for key in sorted(self._defaults):
328 print ' %s = %r' % (key, self._defaults[key])
329 else:
330 print ' No defaults'
331 print '-'*40
332 finally:
333 self._lock.release()
335 def __getattr__(self, suffix):
336 """Dynamic attribute access.
338 Args:
339 suffix: The attribute name.
341 Returns:
342 A configuration values.
344 Raises:
345 AttributeError if the suffix is not a registered suffix.
347 The first time an attribute is referenced, this method is invoked.
348 The value returned taken either from the config module or from the
349 registered default.
351 self._lock.acquire()
352 try:
353 if not self._initialized:
354 self._update_configs()
355 if suffix in self._overrides:
356 value = self._overrides[suffix]
357 elif suffix in self._defaults:
358 value = self._defaults[suffix]
359 else:
360 raise AttributeError(suffix)
362 setattr(self, suffix, value)
363 return value
364 finally:
365 self._lock.release()
369 _default_registry = LibConfigRegistry(DEFAULT_MODNAME)
372 def register(prefix, mapping):
373 """Register a set of configurations with the default config module.
375 Args:
376 prefix: A shared prefix for the configuration names being registered.
377 If the prefix doesn't end in '_', that character is appended.
378 mapping: A dict mapping suffix strings to default values.
380 Returns:
381 A ConfigHandle instance.
383 return _default_registry.register(prefix, mapping)
386 def main():
387 """CGI-style request handler to dump the configuration.
389 Put this in your app.yaml to enable (you can pick any URL):
391 - url: /lib_config
392 script: $PYTHON_LIB/google/appengine/api/lib_config.py
394 Note: unless you are using the SDK, you must be admin.
396 if not os.getenv('SERVER_SOFTWARE', '').startswith('Dev'):
397 from google.appengine.api import users
398 if not users.is_current_user_admin():
399 if users.get_current_user() is None:
400 print 'Status: 302'
401 print 'Location:', users.create_login_url(os.getenv('PATH_INFO', ''))
402 else:
403 print 'Status: 403'
404 print
405 print 'Forbidden'
406 return
408 print 'Content-type: text/plain'
409 print
410 _default_registry._dump()
413 if __name__ == '__main__':
414 main()