|
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() |