thirdparty/google_appengine/google/appengine/api/memcache/memcache_stub.py
changeset 109 620f9b141567
child 149 f2e327a7c5de
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thirdparty/google_appengine/google/appengine/api/memcache/memcache_stub.py	Tue Aug 26 21:49:54 2008 +0000
@@ -0,0 +1,282 @@
+#!/usr/bin/env python
+#
+# Copyright 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Stub version of the memcache API, keeping all data in process memory."""
+
+
+
+import logging
+import time
+
+from google.appengine.api import memcache
+from google.appengine.api.memcache import memcache_service_pb
+
+MemcacheSetResponse = memcache_service_pb.MemcacheSetResponse
+MemcacheSetRequest = memcache_service_pb.MemcacheSetRequest
+MemcacheIncrementRequest = memcache_service_pb.MemcacheIncrementRequest
+MemcacheDeleteResponse = memcache_service_pb.MemcacheDeleteResponse
+
+
+class CacheEntry(object):
+  """An entry in the cache."""
+
+  def __init__(self, value, expiration, flags, gettime):
+    """Initializer.
+
+    Args:
+      value: String containing the data for this entry.
+      expiration: Number containing the expiration time or offset in seconds
+        for this entry.
+      flags: Opaque flags used by the memcache implementation.
+      gettime: Used for testing. Function that works like time.time().
+    """
+    assert isinstance(value, basestring)
+    assert len(value) <= memcache.MAX_VALUE_SIZE
+    assert isinstance(expiration, (int, long))
+
+    self._gettime = gettime
+    self.value = value
+    self.flags = flags
+    self.created_time = self._gettime()
+    self.will_expire = expiration != 0
+    self.locked = False
+    self._SetExpiration(expiration)
+
+  def _SetExpiration(self, expiration):
+    """Sets the expiration for this entry.
+
+    Args:
+      expiration: Number containing the expiration time or offset in seconds
+        for this entry. If expiration is above one month, then it's considered
+        an absolute time since the UNIX epoch.
+    """
+    if expiration > (86400 * 30):
+      self.expiration_time = expiration
+    else:
+      self.expiration_time = self._gettime() + expiration
+
+  def CheckExpired(self):
+    """Returns True if this entry has expired; False otherwise."""
+    return self.will_expire and self._gettime() >= self.expiration_time
+
+  def ExpireAndLock(self, timeout):
+    """Marks this entry as deleted and locks it for the expiration time.
+
+    Used to implement memcache's delete timeout behavior.
+
+    Args:
+      timeout: Parameter originally passed to memcache.delete or
+        memcache.delete_multi to control deletion timeout.
+    """
+    self.will_expire = True
+    self.locked = True
+    self._SetExpiration(timeout)
+
+  def CheckLocked(self):
+    """Returns True if this entry was deleted but has not yet timed out."""
+    return self.locked and not self.CheckExpired()
+
+
+class MemcacheServiceStub(object):
+  """Python only memcache service stub.
+
+  This stub keeps all data in the local process' memory, not in any
+  external servers.
+  """
+
+  def __init__(self, gettime=time.time):
+    """Initializer.
+
+    Args:
+      gettime: time.time()-like function used for testing.
+    """
+    self._gettime = gettime
+    self._ResetStats()
+
+    self._the_cache = {}
+
+  def _ResetStats(self):
+    """Resets statistics information."""
+    self._hits = 0
+    self._misses = 0
+    self._byte_hits = 0
+
+  def MakeSyncCall(self, service, call, request, response):
+    """The main RPC entry point.
+
+    Args:
+      service: Must be name as defined by sub class variable SERVICE.
+      call: A string representing the rpc to make.  Must be part of
+        MemcacheService.
+      request: A protocol buffer of the type corresponding to 'call'.
+      response: A protocol buffer of the type corresponding to 'call'.
+    """
+    assert service == 'memcache'
+    assert request.IsInitialized()
+
+    attr = getattr(self, '_Dynamic_' + call)
+    attr(request, response)
+
+  def _GetKey(self, key):
+    """Retrieves a CacheEntry from the cache if it hasn't expired.
+
+    Does not take deletion timeout into account.
+
+    Args:
+      key: The key to retrieve from the cache.
+
+    Returns:
+      The corresponding CacheEntry instance, or None if it was not found or
+      has already expired.
+    """
+    entry = self._the_cache.get(key, None)
+    if entry is None:
+      return None
+    elif entry.CheckExpired():
+      del self._the_cache[key]
+      return None
+    else:
+      return entry
+
+  def _Dynamic_Get(self, request, response):
+    """Implementation of MemcacheService::Get().
+
+    Args:
+      request: A MemcacheGetRequest.
+      response: A MemcacheGetResponse.
+    """
+    keys = set(request.key_list())
+    for key in keys:
+      entry = self._GetKey(key)
+      if entry is None or entry.CheckLocked():
+        self._misses += 1
+        continue
+      self._hits += 1
+      self._byte_hits += len(entry.value)
+      item = response.add_item()
+      item.set_key(key)
+      item.set_value(entry.value)
+      item.set_flags(entry.flags)
+
+  def _Dynamic_Set(self, request, response):
+    """Implementation of MemcacheService::Set().
+
+    Args:
+      request: A MemcacheSetRequest.
+      response: A MemcacheSetResponse.
+    """
+    for item in request.item_list():
+      key = item.key()
+      set_policy = item.set_policy()
+      old_entry = self._GetKey(key)
+
+      set_status = MemcacheSetResponse.NOT_STORED
+      if ((set_policy == MemcacheSetRequest.SET) or
+          (set_policy == MemcacheSetRequest.ADD and old_entry is None) or
+          (set_policy == MemcacheSetRequest.REPLACE and old_entry is not None)):
+
+        if (old_entry is None or
+            set_policy == MemcacheSetRequest.SET
+            or not old_entry.CheckLocked()):
+          self._the_cache[key] = CacheEntry(item.value(),
+                                            item.expiration_time(),
+                                            item.flags(),
+                                            gettime=self._gettime)
+          set_status = MemcacheSetResponse.STORED
+
+      response.add_set_status(set_status)
+
+  def _Dynamic_Delete(self, request, response):
+    """Implementation of MemcacheService::Delete().
+
+    Args:
+      request: A MemcacheDeleteRequest.
+      response: A MemcacheDeleteResponse.
+    """
+    for item in request.item_list():
+      key = item.key()
+      entry = self._GetKey(key)
+
+      delete_status = MemcacheDeleteResponse.DELETED
+      if entry is None:
+        delete_status = MemcacheDeleteResponse.NOT_FOUND
+      elif item.delete_time == 0:
+        del self._the_cache[key]
+      else:
+        entry.ExpireAndLock(item.delete_time())
+
+      response.add_delete_status(delete_status)
+
+  def _Dynamic_Increment(self, request, response):
+    """Implementation of MemcacheService::Increment().
+
+    Args:
+      request: A MemcacheIncrementRequest.
+      response: A MemcacheIncrementResponse.
+    """
+    key = request.key()
+    entry = self._GetKey(key)
+    if entry is None:
+      return
+
+    try:
+      old_value = long(entry.value)
+    except ValueError, e:
+      logging.error('Increment/decrement failed: Could not interpret '
+                    'value for key = "%s" as an integer.', key)
+      return
+
+    delta = request.delta()
+    if request.direction() == MemcacheIncrementRequest.DECREMENT:
+      delta = -delta
+
+    new_value = old_value + delta
+    if not (0 <= new_value < 2**64):
+      new_value = 0
+
+    entry.value = str(new_value)
+    response.set_new_value(new_value)
+
+  def _Dynamic_FlushAll(self, request, response):
+    """Implementation of MemcacheService::FlushAll().
+
+    Args:
+      request: A MemcacheFlushRequest.
+      response: A MemcacheFlushResponse.
+    """
+    self._the_cache.clear()
+    self._ResetStats()
+
+  def _Dynamic_Stats(self, request, response):
+    """Implementation of MemcacheService::Stats().
+
+    Args:
+      request: A MemcacheStatsRequest.
+      response: A MemcacheStatsResponse.
+    """
+    stats = response.mutable_stats()
+    stats.set_hits(self._hits)
+    stats.set_misses(self._misses)
+    stats.set_byte_hits(self._byte_hits)
+    stats.set_items(len(self._the_cache))
+
+    total_bytes = 0
+    for key, entry in self._the_cache.iteritems():
+      total_bytes += len(entry.value)
+    stats.set_bytes(total_bytes)
+
+    stats.set_oldest_item_age(1800)