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
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
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)
65 from google.appengine.api import lib_config
67 config_handle = lib_config.register(
69 {'add_middleware': lambda app: app})
71 def add_middleware(app):
72 return config_handle.add_middleware(app)
77 __all__
= ['DEFAULT_MODNAME',
91 DEFAULT_MODNAME
= 'appengine_config'
97 class LibConfigRegistry(object):
98 """A registry for library configuration values."""
100 def __init__(self
, modname
):
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
= {}
113 self
._lock
= threading
.RLock()
115 def register(self
, prefix
, mapping
):
116 """Register a set of configuration names.
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.
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('_'):
133 handle
= self
._registrations
.get(prefix
)
135 handle
= ConfigHandle(prefix
, self
)
136 self
._registrations
[prefix
] = handle
139 handle
._update
_defaults
(mapping
)
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.
157 import_func: Used for dependency injection.
161 if (self
._module
is not None and
162 self
._module
is sys
.modules
.get(self
._modname
)):
165 import_func(self
._modname
)
166 except ImportError, err
:
167 if str(err
) != 'No module named %s' % self
._modname
:
170 self
._module
= object()
171 sys
.modules
[self
._modname
] = self
._module
173 self
._module
= sys
.modules
[self
._modname
]
178 """Drops the imported config module.
180 If the config module has not been imported then this is a no-op.
184 if self
._module
is None:
189 handles
= self
._registrations
.values()
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.
199 prefix: A prefix string ending in '_', e.g. 'mylib_'.
202 (key, value) pairs where key is the configuration name with
203 prefix removed, and value is the corresponding value.
207 mapping
= getattr(self
._module
, '__dict__', None)
210 items
= mapping
.items()
214 for key
, value
in items
:
215 if key
.startswith(prefix
):
216 yield key
[nskip
:], value
219 """Print info about all registrations to stdout."""
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
229 print 'Registrations in %s.py:' % self
._modname
231 handles
= self
._registrations
.items()
234 for _
, handle
in sorted(handles
):
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.
248 def __init__(self
, prefix
, registry
):
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
260 self
._registry
= registry
261 self
._lock
= threading
.RLock()
263 def _update_defaults(self
, mapping
):
264 """Update the default mappings.
267 mapping: A dict mapping suffix strings to default values.
271 for key
, value
in mapping
.iteritems():
272 if key
.startswith('__') and key
.endswith('__'):
274 self
._defaults
[key
] = value
275 if self
._initialized
:
276 self
._update
_configs
()
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.
288 if self
._initialized
:
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
)
295 self
._overrides
[key
] = value
296 self
._initialized
= True
300 def _clear_cache(self
):
301 """Clear the cached values."""
304 self
._initialized
= False
305 for key
in self
._defaults
:
306 self
._overrides
.pop(key
, None)
309 except AttributeError:
315 """Print info about this set of registrations to stdout."""
318 print 'Prefix %s:' % self
._prefix
321 for key
in sorted(self
._overrides
):
322 print ' %s = %r' % (key
, self
._overrides
[key
])
324 print ' No overrides'
327 for key
in sorted(self
._defaults
):
328 print ' %s = %r' % (key
, self
._defaults
[key
])
335 def __getattr__(self
, suffix
):
336 """Dynamic attribute access.
339 suffix: The attribute name.
342 A configuration values.
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
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
]
360 raise AttributeError(suffix
)
362 setattr(self
, suffix
, value
)
369 _default_registry
= LibConfigRegistry(DEFAULT_MODNAME
)
372 def register(prefix
, mapping
):
373 """Register a set of configurations with the default config module.
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.
381 A ConfigHandle instance.
383 return _default_registry
.register(prefix
, mapping
)
387 """CGI-style request handler to dump the configuration.
389 Put this in your app.yaml to enable (you can pick any URL):
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:
401 print 'Location:', users
.create_login_url(os
.getenv('PATH_INFO', ''))
408 print 'Content-type: text/plain'
410 _default_registry
._dump
()
413 if __name__
== '__main__':