91 """ |
96 """ |
92 |
97 |
93 def generate(self, template_name, template_values={}): |
98 def generate(self, template_name, template_values={}): |
94 base_path = self.base_path() |
99 base_path = self.base_path() |
95 values = { |
100 values = { |
|
101 'application_name': self.request.environ['APPLICATION_ID'], |
96 'user': users.get_current_user(), |
102 'user': users.get_current_user(), |
97 'request': self.request, |
103 'request': self.request, |
98 'home_path': base_path + DefaultPageHandler.PATH, |
104 'home_path': base_path + DefaultPageHandler.PATH, |
99 'datastore_path': base_path + DatastoreQueryHandler.PATH, |
105 'datastore_path': base_path + DatastoreQueryHandler.PATH, |
100 'datastore_edit_path': base_path + DatastoreEditHandler.PATH, |
106 'datastore_edit_path': base_path + DatastoreEditHandler.PATH, |
101 'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH, |
107 'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH, |
102 'interactive_path': base_path + InteractivePageHandler.PATH, |
108 'interactive_path': base_path + InteractivePageHandler.PATH, |
103 'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH, |
109 'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH, |
|
110 'memcache_path': base_path + MemcachePageHandler.PATH, |
104 } |
111 } |
105 values.update(template_values) |
112 values.update(template_values) |
106 directory = os.path.dirname(__file__) |
113 directory = os.path.dirname(__file__) |
107 path = os.path.join(directory, os.path.join('templates', template_name)) |
114 path = os.path.join(directory, os.path.join('templates', template_name)) |
108 self.response.out.write(template.render(path, values, debug=_DEBUG)) |
115 self.response.out.write(template.render(path, values, debug=_DEBUG)) |
164 """ |
171 """ |
165 |
172 |
166 PATH = InteractivePageHandler.PATH + '/execute' |
173 PATH = InteractivePageHandler.PATH + '/execute' |
167 |
174 |
168 def post(self): |
175 def post(self): |
169 self.response.headers['Content-Type'] = 'text/plain' |
|
170 |
|
171 save_stdout = sys.stdout |
176 save_stdout = sys.stdout |
|
177 results_io = cStringIO.StringIO() |
172 try: |
178 try: |
173 sys.stdout = self.response.out |
179 sys.stdout = results_io |
174 |
180 |
175 code = self.request.get('code') |
181 code = self.request.get('code') |
176 code = code.replace("\r\n", "\n") |
182 code = code.replace("\r\n", "\n") |
177 |
183 |
178 try: |
184 try: |
179 compiled_code = compile(code, '<string>', 'exec') |
185 compiled_code = compile(code, '<string>', 'exec') |
180 exec(compiled_code, globals()) |
186 exec(compiled_code, globals()) |
181 except Exception, e: |
187 except Exception, e: |
182 lines = traceback.format_exception(*sys.exc_info()) |
188 traceback.print_exc(file=results_io) |
183 self.response.out.write(''.join(lines)) |
|
184 finally: |
189 finally: |
185 sys.stdout = save_stdout |
190 sys.stdout = save_stdout |
|
191 |
|
192 results = results_io.getvalue() |
|
193 self.generate('interactive-output.html', {'output': results}) |
|
194 |
|
195 |
|
196 class MemcachePageHandler(BaseRequestHandler): |
|
197 """Shows stats about memcache and query form to get values.""" |
|
198 PATH = '/memcache' |
|
199 |
|
200 TYPES = ((str, str, 'String'), |
|
201 (unicode, unicode, 'Unicode String'), |
|
202 (bool, lambda value: MemcachePageHandler._ToBool(value), 'Boolean'), |
|
203 (int, int, 'Integer'), |
|
204 (long, long, 'Long Integer'), |
|
205 (float, float, 'Float')) |
|
206 DEFAULT_TYPESTR_FOR_NEW = 'String' |
|
207 |
|
208 @staticmethod |
|
209 def _ToBool(string_value): |
|
210 """Convert string to boolean value. |
|
211 |
|
212 Args: |
|
213 string_value: A string. |
|
214 |
|
215 Returns: |
|
216 Boolean. True if string_value is "true", False if string_value is |
|
217 "false". This is case-insensitive. |
|
218 |
|
219 Raises: |
|
220 ValueError: string_value not "true" or "false". |
|
221 """ |
|
222 string_value_low = string_value.lower() |
|
223 if string_value_low not in ('false', 'true'): |
|
224 raise ValueError('invalid literal for boolean: %s' % string_value) |
|
225 return string_value_low == 'true' |
|
226 |
|
227 def _GetValueAndType(self, key): |
|
228 """Fetch value from memcache and detect its type. |
|
229 |
|
230 Args: |
|
231 key: String |
|
232 |
|
233 Returns: |
|
234 (value, type), value is a Python object or None if the key was not set in |
|
235 the cache, type is a string describing the type of the value. |
|
236 """ |
|
237 try: |
|
238 value = memcache.get(key) |
|
239 except (pickle.UnpicklingError, AttributeError, EOFError, ImportError, |
|
240 IndexError), e: |
|
241 msg = 'Failed to retrieve value from cache: %s' % e |
|
242 return msg, 'error' |
|
243 |
|
244 if value is None: |
|
245 return None, self.DEFAULT_TYPESTR_FOR_NEW |
|
246 |
|
247 for typeobj, _, typestr in self.TYPES: |
|
248 if isinstance(value, typeobj): |
|
249 break |
|
250 else: |
|
251 typestr = 'pickled' |
|
252 value = pprint.pformat(value, indent=2) |
|
253 |
|
254 return value, typestr |
|
255 |
|
256 def _SetValue(self, key, type_, value): |
|
257 """Convert a string value and store the result in memcache. |
|
258 |
|
259 Args: |
|
260 key: String |
|
261 type_: String, describing what type the value should have in the cache. |
|
262 value: String, will be converted according to type_. |
|
263 |
|
264 Returns: |
|
265 Result of memcache.set(ket, converted_value). True if value was set. |
|
266 |
|
267 Raises: |
|
268 ValueError: Value can't be converted according to type_. |
|
269 """ |
|
270 for _, converter, typestr in self.TYPES: |
|
271 if typestr == type_: |
|
272 value = converter(value) |
|
273 break |
|
274 else: |
|
275 raise ValueError('Type %s not supported.' % type_) |
|
276 return memcache.set(key, value) |
|
277 |
|
278 def get(self): |
|
279 """Show template and prepare stats and/or key+value to display/edit.""" |
|
280 values = {'request': self.request, |
|
281 'message': self.request.get('message')} |
|
282 |
|
283 edit = self.request.get('edit') |
|
284 key = self.request.get('key') |
|
285 if edit: |
|
286 key = edit |
|
287 values['show_stats'] = False |
|
288 values['show_value'] = False |
|
289 values['show_valueform'] = True |
|
290 values['types'] = [typestr for _, _, typestr in self.TYPES] |
|
291 elif key: |
|
292 values['show_stats'] = True |
|
293 values['show_value'] = True |
|
294 values['show_valueform'] = False |
|
295 else: |
|
296 values['show_stats'] = True |
|
297 values['show_valueform'] = False |
|
298 values['show_value'] = False |
|
299 |
|
300 if key: |
|
301 values['key'] = key |
|
302 values['value'], values['type'] = self._GetValueAndType(key) |
|
303 values['key_exists'] = values['value'] is not None |
|
304 |
|
305 if values['type'] in ('pickled', 'error'): |
|
306 values['writable'] = False |
|
307 else: |
|
308 values['writable'] = True |
|
309 |
|
310 if values['show_stats']: |
|
311 memcache_stats = memcache.get_stats() |
|
312 values['stats'] = memcache_stats |
|
313 try: |
|
314 hitratio = memcache_stats['hits'] * 100 / (memcache_stats['hits'] |
|
315 + memcache_stats['misses']) |
|
316 except ZeroDivisionError: |
|
317 hitratio = 0 |
|
318 values['hitratio'] = hitratio |
|
319 delta_t = datetime.timedelta(seconds=memcache_stats['oldest_item_age']) |
|
320 values['oldest_item_age'] = datetime.datetime.now() - delta_t |
|
321 |
|
322 self.generate('memcache.html', values) |
|
323 |
|
324 def _urlencode(self, query): |
|
325 """Encode a dictionary into a URL query string. |
|
326 |
|
327 In contrast to urllib this encodes unicode characters as UTF8. |
|
328 |
|
329 Args: |
|
330 query: Dictionary of key/value pairs. |
|
331 |
|
332 Returns: |
|
333 String. |
|
334 """ |
|
335 return '&'.join('%s=%s' % (urllib.quote_plus(k.encode('utf8')), |
|
336 urllib.quote_plus(v.encode('utf8'))) |
|
337 for k, v in query.iteritems()) |
|
338 |
|
339 def post(self): |
|
340 """Handle modifying actions and/or redirect to GET page.""" |
|
341 next_param = {} |
|
342 |
|
343 if self.request.get('action:flush'): |
|
344 if memcache.flush_all(): |
|
345 next_param['message'] = 'Cache flushed, all keys dropped.' |
|
346 else: |
|
347 next_param['message'] = 'Flushing the cache failed. Please try again.' |
|
348 |
|
349 elif self.request.get('action:display'): |
|
350 next_param['key'] = self.request.get('key') |
|
351 |
|
352 elif self.request.get('action:edit'): |
|
353 next_param['edit'] = self.request.get('key') |
|
354 |
|
355 elif self.request.get('action:delete'): |
|
356 key = self.request.get('key') |
|
357 result = memcache.delete(key) |
|
358 if result == memcache.DELETE_NETWORK_FAILURE: |
|
359 next_param['message'] = ('ERROR: Network failure, key "%s" not deleted.' |
|
360 % key) |
|
361 elif result == memcache.DELETE_ITEM_MISSING: |
|
362 next_param['message'] = 'Key "%s" not in cache.' % key |
|
363 elif result == memcache.DELETE_SUCCESSFUL: |
|
364 next_param['message'] = 'Key "%s" deleted.' % key |
|
365 else: |
|
366 next_param['message'] = ('Unknown return value. Key "%s" might still ' |
|
367 'exist.' % key) |
|
368 |
|
369 elif self.request.get('action:save'): |
|
370 key = self.request.get('key') |
|
371 value = self.request.get('value') |
|
372 type_ = self.request.get('type') |
|
373 next_param['key'] = key |
|
374 try: |
|
375 if self._SetValue(key, type_, value): |
|
376 next_param['message'] = 'Key "%s" saved.' % key |
|
377 else: |
|
378 next_param['message'] = 'ERROR: Failed to save key "%s".' % key |
|
379 except ValueError, e: |
|
380 next_param['message'] = 'ERROR: Unable to encode value: %s' % e |
|
381 |
|
382 elif self.request.get('action:cancel'): |
|
383 next_param['key'] = self.request.get('key') |
|
384 |
|
385 else: |
|
386 next_param['message'] = 'Unknown action.' |
|
387 |
|
388 next = self.request.path_url |
|
389 if next_param: |
|
390 next = '%s?%s' % (next, self._urlencode(next_param)) |
|
391 self.redirect(next) |
186 |
392 |
187 |
393 |
188 class DatastoreRequestHandler(BaseRequestHandler): |
394 class DatastoreRequestHandler(BaseRequestHandler): |
189 """The base request handler for our datastore admin pages. |
395 """The base request handler for our datastore admin pages. |
190 |
396 |