thirdparty/google_appengine/google/appengine/ext/admin/__init__.py
changeset 149 f2e327a7c5de
parent 109 620f9b141567
child 297 35211afcd563
--- a/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py	Tue Sep 16 01:18:49 2008 +0000
+++ b/thirdparty/google_appengine/google/appengine/ext/admin/__init__.py	Tue Sep 16 02:28:33 2008 +0000
@@ -28,7 +28,10 @@
 import logging
 import math
 import mimetypes
+import os
 import os.path
+import pickle
+import pprint
 import random
 import sys
 import time
@@ -39,8 +42,10 @@
 import wsgiref.handlers
 
 from google.appengine.api import datastore
+from google.appengine.api import datastore_admin
 from google.appengine.api import datastore_types
 from google.appengine.api import datastore_errors
+from google.appengine.api import memcache
 from google.appengine.api import users
 from google.appengine.ext import db
 from google.appengine.ext import webapp
@@ -93,6 +98,7 @@
   def generate(self, template_name, template_values={}):
     base_path = self.base_path()
     values = {
+      'application_name': self.request.environ['APPLICATION_ID'],
       'user': users.get_current_user(),
       'request': self.request,
       'home_path': base_path + DefaultPageHandler.PATH,
@@ -101,6 +107,7 @@
       'datastore_batch_edit_path': base_path + DatastoreBatchEditHandler.PATH,
       'interactive_path': base_path + InteractivePageHandler.PATH,
       'interactive_execute_path': base_path + InteractiveExecuteHandler.PATH,
+      'memcache_path': base_path + MemcachePageHandler.PATH,
     }
     values.update(template_values)
     directory = os.path.dirname(__file__)
@@ -166,11 +173,10 @@
   PATH = InteractivePageHandler.PATH + '/execute'
 
   def post(self):
-    self.response.headers['Content-Type'] = 'text/plain'
-
     save_stdout = sys.stdout
+    results_io = cStringIO.StringIO()
     try:
-      sys.stdout = self.response.out
+      sys.stdout = results_io
 
       code = self.request.get('code')
       code = code.replace("\r\n", "\n")
@@ -179,11 +185,211 @@
         compiled_code = compile(code, '<string>', 'exec')
         exec(compiled_code, globals())
       except Exception, e:
-        lines = traceback.format_exception(*sys.exc_info())
-        self.response.out.write(''.join(lines))
+        traceback.print_exc(file=results_io)
     finally:
       sys.stdout = save_stdout
 
+    results = results_io.getvalue()
+    self.generate('interactive-output.html', {'output': results})
+
+
+class MemcachePageHandler(BaseRequestHandler):
+  """Shows stats about memcache and query form to get values."""
+  PATH = '/memcache'
+
+  TYPES = ((str, str, 'String'),
+           (unicode, unicode, 'Unicode String'),
+           (bool, lambda value: MemcachePageHandler._ToBool(value), 'Boolean'),
+           (int, int, 'Integer'),
+           (long, long, 'Long Integer'),
+           (float, float, 'Float'))
+  DEFAULT_TYPESTR_FOR_NEW = 'String'
+
+  @staticmethod
+  def _ToBool(string_value):
+    """Convert string to boolean value.
+
+    Args:
+      string_value: A string.
+
+    Returns:
+      Boolean.  True if string_value is "true", False if string_value is
+      "false".  This is case-insensitive.
+
+    Raises:
+      ValueError: string_value not "true" or "false".
+    """
+    string_value_low = string_value.lower()
+    if string_value_low not in ('false', 'true'):
+      raise ValueError('invalid literal for boolean: %s' % string_value)
+    return string_value_low == 'true'
+
+  def _GetValueAndType(self, key):
+    """Fetch value from memcache and detect its type.
+
+    Args:
+      key: String
+
+    Returns:
+      (value, type), value is a Python object or None if the key was not set in
+      the cache, type is a string describing the type of the value.
+    """
+    try:
+      value = memcache.get(key)
+    except (pickle.UnpicklingError, AttributeError, EOFError, ImportError,
+            IndexError), e:
+      msg = 'Failed to retrieve value from cache: %s' % e
+      return msg, 'error'
+
+    if value is None:
+      return None, self.DEFAULT_TYPESTR_FOR_NEW
+
+    for typeobj, _, typestr in self.TYPES:
+      if isinstance(value, typeobj):
+        break
+    else:
+      typestr = 'pickled'
+      value = pprint.pformat(value, indent=2)
+
+    return value, typestr
+
+  def _SetValue(self, key, type_, value):
+    """Convert a string value and store the result in memcache.
+
+    Args:
+      key: String
+      type_: String, describing what type the value should have in the cache.
+      value: String, will be converted according to type_.
+
+    Returns:
+      Result of memcache.set(ket, converted_value).  True if value was set.
+
+    Raises:
+      ValueError: Value can't be converted according to type_.
+    """
+    for _, converter, typestr in self.TYPES:
+      if typestr == type_:
+        value = converter(value)
+        break
+    else:
+      raise ValueError('Type %s not supported.' % type_)
+    return memcache.set(key, value)
+
+  def get(self):
+    """Show template and prepare stats and/or key+value to display/edit."""
+    values = {'request': self.request,
+              'message': self.request.get('message')}
+
+    edit = self.request.get('edit')
+    key = self.request.get('key')
+    if edit:
+      key = edit
+      values['show_stats'] = False
+      values['show_value'] = False
+      values['show_valueform'] = True
+      values['types'] = [typestr for _, _, typestr in self.TYPES]
+    elif key:
+      values['show_stats'] = True
+      values['show_value'] = True
+      values['show_valueform'] = False
+    else:
+      values['show_stats'] = True
+      values['show_valueform'] = False
+      values['show_value'] = False
+
+    if key:
+      values['key'] = key
+      values['value'], values['type'] = self._GetValueAndType(key)
+      values['key_exists'] = values['value'] is not None
+
+      if values['type'] in ('pickled', 'error'):
+        values['writable'] = False
+      else:
+        values['writable'] = True
+
+    if values['show_stats']:
+      memcache_stats = memcache.get_stats()
+      values['stats'] = memcache_stats
+      try:
+        hitratio = memcache_stats['hits'] * 100 / (memcache_stats['hits']
+                                                   + memcache_stats['misses'])
+      except ZeroDivisionError:
+        hitratio = 0
+      values['hitratio'] = hitratio
+      delta_t = datetime.timedelta(seconds=memcache_stats['oldest_item_age'])
+      values['oldest_item_age'] = datetime.datetime.now() - delta_t
+
+    self.generate('memcache.html', values)
+
+  def _urlencode(self, query):
+    """Encode a dictionary into a URL query string.
+
+    In contrast to urllib this encodes unicode characters as UTF8.
+
+    Args:
+      query: Dictionary of key/value pairs.
+
+    Returns:
+      String.
+    """
+    return '&'.join('%s=%s' % (urllib.quote_plus(k.encode('utf8')),
+                               urllib.quote_plus(v.encode('utf8')))
+                    for k, v in query.iteritems())
+
+  def post(self):
+    """Handle modifying actions and/or redirect to GET page."""
+    next_param = {}
+
+    if self.request.get('action:flush'):
+      if memcache.flush_all():
+        next_param['message'] = 'Cache flushed, all keys dropped.'
+      else:
+        next_param['message'] = 'Flushing the cache failed.  Please try again.'
+
+    elif self.request.get('action:display'):
+      next_param['key'] = self.request.get('key')
+
+    elif self.request.get('action:edit'):
+      next_param['edit'] = self.request.get('key')
+
+    elif self.request.get('action:delete'):
+      key = self.request.get('key')
+      result = memcache.delete(key)
+      if result == memcache.DELETE_NETWORK_FAILURE:
+        next_param['message'] = ('ERROR: Network failure, key "%s" not deleted.'
+                                 % key)
+      elif result == memcache.DELETE_ITEM_MISSING:
+        next_param['message'] = 'Key "%s" not in cache.' % key
+      elif result == memcache.DELETE_SUCCESSFUL:
+        next_param['message'] = 'Key "%s" deleted.' % key
+      else:
+        next_param['message'] = ('Unknown return value.  Key "%s" might still '
+                                 'exist.' % key)
+
+    elif self.request.get('action:save'):
+      key = self.request.get('key')
+      value = self.request.get('value')
+      type_ = self.request.get('type')
+      next_param['key'] = key
+      try:
+        if self._SetValue(key, type_, value):
+          next_param['message'] = 'Key "%s" saved.' % key
+        else:
+          next_param['message'] = 'ERROR: Failed to save key "%s".' % key
+      except ValueError, e:
+        next_param['message'] = 'ERROR: Unable to encode value: %s' % e
+
+    elif self.request.get('action:cancel'):
+      next_param['key'] = self.request.get('key')
+
+    else:
+      next_param['message'] = 'Unknown action.'
+
+    next = self.request.path_url
+    if next_param:
+      next = '%s?%s' % (next, self._urlencode(next_param))
+    self.redirect(next)
+
 
 class DatastoreRequestHandler(BaseRequestHandler):
   """The base request handler for our datastore admin pages.
@@ -261,6 +467,31 @@
 
   PATH = '/datastore'
 
+  SCHEMA_CACHE_TIMEOUT = 60
+
+  def get_kinds(self, cache={}):
+    """Return sorted list of kind names the datastore knows about.
+
+    The list of kinds is cached for a short time.
+    """
+    server_software = os.environ['SERVER_SOFTWARE']
+    in_production = not server_software.startswith('Development')
+
+    if in_production and ('kinds' in cache):
+      if cache['kinds_timestamp'] + self.SCHEMA_CACHE_TIMEOUT > time.time():
+        return cache['kinds']
+      else:
+        del cache['kinds']
+    schema = datastore_admin.GetSchema()
+    kinds = []
+    for entity_proto in schema:
+      kinds.append(entity_proto.key().path().element_list()[-1].type())
+    kinds.sort()
+    if in_production:
+      cache['kinds'] = kinds
+      cache['kinds_timestamp'] = time.time()
+    return kinds
+
   def get(self):
     """Formats the results from execute_query() for datastore.html.
 
@@ -324,6 +555,7 @@
 
     values = {
       'request': self.request,
+      'kinds': self.get_kinds(),
       'kind': self.request.get('kind'),
       'order': self.request.get('order'),
       'headers': headers,
@@ -857,6 +1089,7 @@
     ('.*' + DatastoreBatchEditHandler.PATH, DatastoreBatchEditHandler),
     ('.*' + InteractivePageHandler.PATH, InteractivePageHandler),
     ('.*' + InteractiveExecuteHandler.PATH, InteractiveExecuteHandler),
+    ('.*' + MemcachePageHandler.PATH, MemcachePageHandler),
     ('.*' + ImageHandler.PATH, ImageHandler),
     ('.*', DefaultPageHandler),
   ], debug=_DEBUG)