thirdparty/google_appengine/google/appengine/api/memcache/__init__.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/__init__.py	Tue Aug 26 21:49:54 2008 +0000
@@ -0,0 +1,789 @@
+#!/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.
+#
+
+"""Memcache API.
+
+Provides memcached-alike API to application developers to store
+data in memory when reliable storage via the DataStore API isn't
+required and higher performance is desired.
+"""
+
+
+
+import cStringIO
+import math
+import pickle
+import types
+
+from google.appengine.api import api_base_pb
+from google.appengine.api import apiproxy_stub_map
+from google.appengine.api.memcache import memcache_service_pb
+from google.appengine.runtime import apiproxy_errors
+
+MemcacheSetResponse = memcache_service_pb.MemcacheSetResponse
+MemcacheSetRequest = memcache_service_pb.MemcacheSetRequest
+
+MemcacheGetResponse = memcache_service_pb.MemcacheGetResponse
+MemcacheGetRequest = memcache_service_pb.MemcacheGetRequest
+
+MemcacheDeleteResponse = memcache_service_pb.MemcacheDeleteResponse
+MemcacheDeleteRequest = memcache_service_pb.MemcacheDeleteRequest
+
+MemcacheIncrementResponse = memcache_service_pb.MemcacheIncrementResponse
+MemcacheIncrementRequest = memcache_service_pb.MemcacheIncrementRequest
+
+MemcacheFlushResponse = memcache_service_pb.MemcacheFlushResponse
+MemcacheFlushRequest = memcache_service_pb.MemcacheFlushRequest
+
+MemcacheStatsRequest = memcache_service_pb.MemcacheStatsRequest
+MemcacheStatsResponse = memcache_service_pb.MemcacheStatsResponse
+
+DELETE_NETWORK_FAILURE = 0
+DELETE_ITEM_MISSING = 1
+DELETE_SUCCESSFUL = 2
+
+MAX_KEY_SIZE = 250
+MAX_VALUE_SIZE = 10 ** 6
+
+STAT_HITS = 'hits'
+STAT_MISSES = 'misses'
+STAT_BYTE_HITS = 'byte_hits'
+STAT_ITEMS = 'items'
+STAT_BYTES = 'bytes'
+STAT_OLDEST_ITEM_AGES = 'oldest_item_age'
+
+FLAG_TYPE_MASK = 7
+FLAG_COMPRESSED = 1 << 3
+
+TYPE_STR = 0
+TYPE_UNICODE = 1
+TYPE_PICKLED = 2
+TYPE_INT = 3
+TYPE_LONG = 4
+
+
+def _key_string(key, key_prefix='', server_to_user_dict=None):
+  """Utility function to handle different ways of requesting keys.
+
+  Args:
+    key: Either a string or tuple of (shard_number, string).  In Google App
+      Engine the sharding is automatic so the shard number is ignored.
+      To memcache, the key is just bytes (no defined encoding).
+    key_prefix: Optional string prefix to prepend to key.
+    server_to_user_dict: Optional dictionary to populate with a mapping of
+      server-side key (which includes the key_prefix) to user-supplied key
+      (which does not have the prefix).
+
+  Returns:
+    The key as a non-unicode string prepended with key_prefix. This is the key
+    sent to and stored by the server.
+
+  Raises:
+    TypeError: If provided key isn't a string or tuple of (int, string)
+      or key_prefix or server_to_user_dict are of the wrong type.
+    ValueError: If the key, when translated to the server key, is more than
+      250 bytes in length.
+  """
+  if type(key) is types.TupleType:
+    key = key[1]
+  if not isinstance(key, basestring):
+    raise TypeError('Key must be a string instance, received %r' % key)
+  if not isinstance(key_prefix, basestring):
+    raise TypeError('key_prefix must be a string instance, received %r' %
+                    key_prefix)
+
+  server_key = key_prefix + key
+  if isinstance(server_key, unicode):
+    server_key = server_key.encode('utf-8')
+
+  if len(server_key) > MAX_KEY_SIZE:
+    raise ValueError('Keys may not be more than %d bytes in length, '
+                     'received %d bytes' % (MAX_KEY_SIZE, len(server_key)))
+
+  if server_to_user_dict is not None:
+    if not isinstance(server_to_user_dict, dict):
+      raise TypeError('server_to_user_dict must be a dict instance, ' +
+                      'received %r' % key)
+    server_to_user_dict[server_key] = key
+
+  return server_key
+
+
+def _validate_encode_value(value, do_pickle):
+  """Utility function to validate and encode server keys and values.
+
+  Args:
+    value: Value to store in memcache. If it's a string, it will get passed
+      along as-is. If it's a unicode string, it will be marked appropriately,
+      such that retrievals will yield a unicode value. If it's any other data
+      type, this function will attempt to pickle the data and then store the
+      serialized result, unpickling it upon retrieval.
+    do_pickle: Callable that takes an object and returns a non-unicode
+      string containing the pickled object.
+
+  Returns:
+    Tuple (stored_value, flags) where:
+      stored_value: The value as a non-unicode string that should be stored
+        in memcache.
+      flags: An integer with bits set from the FLAG_* constants in this file
+        to indicate the encoding of the key and value.
+
+  Raises:
+    ValueError: If the encoded value is too large.
+    pickle.PicklingError: If the value is not a string and could not be pickled.
+    RuntimeError: If a complicated data structure could not be pickled due to
+      too many levels of recursion in its composition.
+  """
+  flags = 0
+  stored_value = value
+
+  if isinstance(value, str):
+    pass
+  elif isinstance(value, unicode):
+    stored_value = value.encode('utf-8')
+    flags |= TYPE_UNICODE
+  elif isinstance(value, int):
+    stored_value = str(value)
+    flags |= TYPE_INT
+  elif isinstance(value, long):
+    stored_value = str(value)
+    flags |= TYPE_LONG
+  else:
+    stored_value = do_pickle(value)
+    flags |= TYPE_PICKLED
+
+
+  if len(stored_value) > MAX_VALUE_SIZE:
+    raise ValueError('Values may not be more than %d bytes in length; '
+                     'received %d bytes' % (MAX_VALUE_SIZE, len(stored_value)))
+
+  return (stored_value, flags)
+
+
+def _decode_value(stored_value, flags, do_unpickle):
+  """Utility function for decoding values retrieved from memcache.
+
+  Args:
+    stored_value: The value as a non-unicode string that was stored.
+    flags: An integer with bits set from the FLAG_* constants in this file
+      that indicate the encoding of the key and value.
+    do_unpickle: Callable that takes a non-unicode string object that contains
+      a pickled object and returns the pickled object.
+
+  Returns:
+    The original object that was stored, be it a normal string, a unicode
+    string, int, long, or a Python object that was pickled.
+
+  Raises:
+    pickle.UnpicklingError: If the value could not be unpickled.
+  """
+  assert isinstance(stored_value, str)
+  assert isinstance(flags, (int, long))
+
+  type_number = flags & FLAG_TYPE_MASK
+  value = stored_value
+
+
+  if type_number == TYPE_STR:
+    return value
+  elif type_number == TYPE_UNICODE:
+    return value.decode('utf-8')
+  elif type_number == TYPE_PICKLED:
+    return do_unpickle(value)
+  elif type_number == TYPE_INT:
+    return int(value)
+  elif type_number == TYPE_LONG:
+    return long(value)
+  else:
+    assert False, "Unknown stored type"
+  assert False, "Shouldn't get here."
+
+class Client(object):
+  """Memcache client object, through which one invokes all memcache operations.
+
+  Several methods are no-ops to retain source-level compatibility
+  with the existing popular Python memcache library.
+
+  Any method that takes a 'key' argument will accept that key as a string
+  (unicode or not) or a tuple of (hash_value, string) where the hash_value,
+  normally used for sharding onto a memcache instance, is instead ignored, as
+  Google App Engine deals with the sharding transparently. Keys in memcache are
+  just bytes, without a specified encoding. All such methods may raise TypeError
+  if provided a bogus key value and a ValueError if the key is too large.
+
+  Any method that takes a 'value' argument will accept as that value any
+  string (unicode or not), int, long, or pickle-able Python object, including
+  all native types.  You'll get back from the cache the same type that you
+  originally put in.
+  """
+
+  def __init__(self, servers=None, debug=0,
+               pickleProtocol=pickle.HIGHEST_PROTOCOL,
+               pickler=pickle.Pickler,
+               unpickler=pickle.Unpickler,
+               pload=None,
+               pid=None,
+               make_sync_call=apiproxy_stub_map.MakeSyncCall):
+    """Create a new Client object.
+
+    No parameters are required.
+
+    Arguments:
+      servers: Ignored; only for compatibility.
+      debug: Ignored; only for compatibility.
+      pickleProtocol: Pickle protocol to use for pickling the object.
+      pickler: pickle.Pickler sub-class to use for pickling.
+      unpickler: pickle.Unpickler sub-class to use for unpickling.
+      pload: Callable to use for retrieving objects by persistent id.
+      pid: Callable to use for determine the persistent id for objects, if any.
+      make_sync_call: Function to use to make an App Engine service call.
+        Used for testing.
+    """
+    self._pickle_data = cStringIO.StringIO()
+    self._pickler_instance = pickler(self._pickle_data,
+                                     protocol=pickleProtocol)
+    self._unpickler_instance = unpickler(self._pickle_data)
+    if pid is not None:
+      self._pickler_instance.persistent_id = pid
+    if pload is not None:
+      self._unpickler_instance.persistent_load = pload
+
+    def DoPickle(value):
+      self._pickle_data.truncate(0)
+      self._pickler_instance.clear_memo()
+      self._pickler_instance.dump(value)
+      return self._pickle_data.getvalue()
+    self._do_pickle = DoPickle
+
+    def DoUnpickle(value):
+      self._pickle_data.truncate(0)
+      self._pickle_data.write(value)
+      self._pickle_data.seek(0)
+      self._unpickler_instance.memo.clear()
+      return self._unpickler_instance.load()
+    self._do_unpickle = DoUnpickle
+
+    self._make_sync_call = make_sync_call
+
+  def set_servers(self, servers):
+    """Sets the pool of memcache servers used by the client.
+
+    This is purely a compatibility method.  In Google App Engine, it's a no-op.
+    """
+    pass
+
+  def disconnect_all(self):
+    """Closes all connections to memcache servers.
+
+    This is purely a compatibility method.  In Google App Engine, it's a no-op.
+    """
+    pass
+
+  def forget_dead_hosts(self):
+    """Resets all servers to the alive status.
+
+    This is purely a compatibility method.  In Google App Engine, it's a no-op.
+    """
+    pass
+
+  def debuglog(self):
+    """Logging function for debugging information.
+
+    This is purely a compatibility method.  In Google App Engine, it's a no-op.
+    """
+    pass
+
+  def get_stats(self):
+    """Gets memcache statistics for this application.
+
+    All of these statistics may reset due to various transient conditions. They
+    provide the best information available at the time of being called.
+
+    Returns:
+      Dictionary mapping statistic names to associated values. Statistics and
+      their associated meanings:
+
+        hits: Number of cache get requests resulting in a cache hit.
+        misses: Number of cache get requests resulting in a cache miss.
+        byte_hits: Sum of bytes transferred on get requests. Rolls over to
+          zero on overflow.
+        items: Number of key/value pairs in the cache.
+        bytes: Total size of all items in the cache.
+        oldest_item_age: How long in seconds since the oldest item in the
+          cache was accessed. Effectively, this indicates how long a new
+          item will survive in the cache without being accessed. This is
+          _not_ the amount of time that has elapsed since the item was
+          created.
+
+      On error, returns None.
+    """
+    request = MemcacheStatsRequest()
+    response = MemcacheStatsResponse()
+    try:
+      self._make_sync_call('memcache', 'Stats', request, response)
+    except apiproxy_errors.ApplicationError, e:
+      return None
+
+    if not response.has_stats():
+      return None
+
+    stats = response.stats()
+    return {
+      STAT_HITS: stats.hits(),
+      STAT_MISSES: stats.misses(),
+      STAT_BYTE_HITS: stats.byte_hits(),
+      STAT_ITEMS: stats.items(),
+      STAT_BYTES: stats.bytes(),
+      STAT_OLDEST_ITEM_AGES: stats.oldest_item_age(),
+    }
+
+  def flush_all(self):
+    """Deletes everything in memcache.
+
+    Returns:
+      True on success, False on RPC or server error.
+    """
+    request = MemcacheFlushRequest()
+    response = MemcacheFlushResponse()
+    try:
+      self._make_sync_call('memcache', 'FlushAll', request, response)
+    except apiproxy_errors.ApplicationError:
+      return False
+    return True
+
+  def get(self, key):
+    """Looks up a single key in memcache.
+
+    If you have multiple items to load, though, it's much more efficient
+    to use get_multi() instead, which loads them in one bulk operation,
+    reducing the networking latency that'd otherwise be required to do
+    many serialized get() operations.
+
+    Args:
+      key: The key in memcache to look up.  See docs on Client
+        for details of format.
+
+    Returns:
+      The value of the key, if found in memcache, else None.
+    """
+    request = MemcacheGetRequest()
+    request.add_key(_key_string(key))
+    response = MemcacheGetResponse()
+    try:
+      self._make_sync_call('memcache', 'Get', request, response)
+    except apiproxy_errors.ApplicationError:
+      return None
+
+    if not response.item_size():
+      return None
+
+    return _decode_value(response.item(0).value(),
+                         response.item(0).flags(),
+                         self._do_unpickle)
+
+  def get_multi(self, keys, key_prefix=''):
+    """Looks up multiple keys from memcache in one operation.
+
+    This is the recommended way to do bulk loads.
+
+    Args:
+      keys: List of keys to look up.  Keys may be strings or
+        tuples of (hash_value, string).  Google App Engine
+        does the sharding and hashing automatically, though, so the hash
+        value is ignored.  To memcache, keys are just series of bytes,
+        and not in any particular encoding.
+      key_prefix: Prefix to prepend to all keys when talking to the server;
+        not included in the returned dictionary.
+
+    Returns:
+      A dictionary of the keys and values that were present in memcache.
+      Even if the key_prefix was specified, that key_prefix won't be on
+      the keys in the returned dictionary.
+    """
+    request = MemcacheGetRequest()
+    response = MemcacheGetResponse()
+    user_key = {}
+    for key in keys:
+      request.add_key(_key_string(key, key_prefix, user_key))
+    try:
+      self._make_sync_call('memcache', 'Get', request, response)
+    except apiproxy_errors.ApplicationError:
+      return {}
+
+    return_value = {}
+    for returned_item in response.item_list():
+      value = _decode_value(returned_item.value(), returned_item.flags(),
+                            self._do_unpickle)
+      return_value[user_key[returned_item.key()]] = value
+    return return_value
+
+  def delete(self, key, seconds=0):
+    """Deletes a key from memcache.
+
+    Args:
+      key: Key to delete.  See docs on Client for detils.
+      seconds: Optional number of seconds to make deleted items 'locked'
+        for 'add' operations. Value can be a delta from current time (up to
+        1 month), or an absolute Unix epoch time.  Defaults to 0, which means
+        items can be immediately added.  With or without this option,
+        a 'set' operation will always work.  Float values will be rounded up to
+        the nearest whole second.
+
+    Returns:
+      DELETE_NETWORK_FAILURE (0) on network failure,
+      DELETE_ITEM_MISSING (1) if the server tried to delete the item but
+      didn't have it, or
+      DELETE_SUCCESSFUL (2) if the item was actually deleted.
+      This can be used as a boolean value, where a network failure is the
+      only bad condition.
+    """
+    if not isinstance(seconds, (int, long, float)):
+      raise TypeError('Delete timeout must be a number.')
+    if seconds < 0:
+      raise ValueError('Delete timeout must be non-negative.')
+
+    request = MemcacheDeleteRequest()
+    response = MemcacheDeleteResponse()
+
+    delete_item = request.add_item()
+    delete_item.set_key(_key_string(key))
+    delete_item.set_delete_time(int(math.ceil(seconds)))
+    try:
+      self._make_sync_call('memcache', 'Delete', request, response)
+    except apiproxy_errors.ApplicationError:
+      return DELETE_NETWORK_FAILURE
+    assert response.delete_status_size() == 1, 'Unexpected status size.'
+
+    if response.delete_status(0) == MemcacheDeleteResponse.DELETED:
+      return DELETE_SUCCESSFUL
+    elif response.delete_status(0) == MemcacheDeleteResponse.NOT_FOUND:
+      return DELETE_ITEM_MISSING
+    assert False, 'Unexpected deletion status code.'
+
+  def delete_multi(self, keys, seconds=0, key_prefix=''):
+    """Delete multiple keys at once.
+
+    Args:
+      keys: List of keys to delete.
+      seconds: Optional number of seconds to make deleted items 'locked'
+        for 'add' operations. Value can be a delta from current time (up to
+        1 month), or an absolute Unix epoch time.  Defaults to 0, which means
+        items can be immediately added.  With or without this option,
+        a 'set' operation will always work.  Float values will be rounded up to
+        the nearest whole second.
+      key_prefix: Prefix to put on all keys when sending specified
+        keys to memcache.  See docs for get_multi() and set_multi().
+
+    Returns:
+      True if all operations completed successfully.  False if one
+      or more failed to complete.
+    """
+    if not isinstance(seconds, (int, long, float)):
+      raise TypeError('Delete timeout must be a number.')
+    if seconds < 0:
+      raise ValueError('Delete timeout must not be negative.')
+
+    request = MemcacheDeleteRequest()
+    response = MemcacheDeleteResponse()
+
+    for key in keys:
+      delete_item = request.add_item()
+      delete_item.set_key(_key_string(key, key_prefix=key_prefix))
+      delete_item.set_delete_time(int(math.ceil(seconds)))
+    try:
+      self._make_sync_call('memcache', 'Delete', request, response)
+    except apiproxy_errors.ApplicationError:
+      return False
+    return True
+
+  def set(self, key, value, time=0, min_compress_len=0):
+    """Sets a key's value, regardless of previous contents in cache.
+
+    Unlike add() and replace(), this method always sets (or
+    overwrites) the value in memcache, regardless of previous
+    contents.
+
+    Args:
+      key: Key to set.  See docs on Client for details.
+      value: Value to set.  Any type.  If complex, will be pickled.
+      time: Optional expiration time, either relative number of seconds
+        from current time (up to 1 month), or an absolute Unix epoch time.
+        By default, items never expire, though items may be evicted due to
+        memory pressure.  Float values will be rounded up to the nearest
+        whole second.
+      min_compress_len: Ignored option for compatibility.
+
+    Returns:
+      True if set.  False on error.
+    """
+    return self._set_with_policy(MemcacheSetRequest.SET, key, value, time=time)
+
+  def add(self, key, value, time=0, min_compress_len=0):
+    """Sets a key's value, iff item is not already in memcache.
+
+    Args:
+      key: Key to set.  See docs on Client for details.
+      value: Value to set.  Any type.  If complex, will be pickled.
+      time: Optional expiration time, either relative number of seconds
+        from current time (up to 1 month), or an absolute Unix epoch time.
+        By default, items never expire, though items may be evicted due to
+        memory pressure.  Float values will be rounded up to the nearest
+        whole second.
+      min_compress_len: Ignored option for compatibility.
+
+    Returns:
+      True if added.  False on error.
+    """
+    return self._set_with_policy(MemcacheSetRequest.ADD, key, value, time=time)
+
+  def replace(self, key, value, time=0, min_compress_len=0):
+    """Replaces a key's value, failing if item isn't already in memcache.
+
+    Args:
+      key: Key to set.  See docs on Client for details.
+      value: Value to set.  Any type.  If complex, will be pickled.
+      time: Optional expiration time, either relative number of seconds
+        from current time (up to 1 month), or an absolute Unix epoch time.
+        By default, items never expire, though items may be evicted due to
+        memory pressure.  Float values will be rounded up to the nearest
+        whole second.
+      min_compress_len: Ignored option for compatibility.
+
+    Returns:
+      True if replaced.  False on RPC error or cache miss.
+    """
+    return self._set_with_policy(MemcacheSetRequest.REPLACE,
+                                 key, value, time=time)
+
+  def _set_with_policy(self, policy, key, value, time=0):
+    """Sets a single key with a specified policy.
+
+    Helper function for set(), add(), and replace().
+
+    Args:
+      policy:  One of MemcacheSetRequest.SET, .ADD, or .REPLACE.
+      key: Key to add, set, or replace.  See docs on Client for details.
+      value: Value to set.
+      time: Expiration time, defaulting to 0 (never expiring).
+
+    Returns:
+      True if stored, False on RPC error or policy error, e.g. a replace
+      that failed due to the item not already existing, or an add
+      failing due to the item not already existing.
+    """
+    if not isinstance(time, (int, long, float)):
+      raise TypeError('Expiration must be a number.')
+    if time < 0:
+      raise ValueError('Expiration must not be negative.')
+
+    request = MemcacheSetRequest()
+    item = request.add_item()
+    item.set_key(_key_string(key))
+    stored_value, flags = _validate_encode_value(value, self._do_pickle)
+    item.set_value(stored_value)
+    item.set_flags(flags)
+    item.set_set_policy(policy)
+    item.set_expiration_time(int(math.ceil(time)))
+    response = MemcacheSetResponse()
+    try:
+      self._make_sync_call('memcache', 'Set', request, response)
+    except apiproxy_errors.ApplicationError:
+      return False
+    if response.set_status_size() != 1:
+      return False
+    return response.set_status(0) == MemcacheSetResponse.STORED
+
+  def set_multi(self, mapping, time=0, key_prefix='', min_compress_len=0):
+    """Set multiple keys' values at once.
+
+    This reduces the network latency of doing many requests in serial.
+
+    Args:
+      mapping: Dictionary of keys to values.
+      time: Optional expiration time, either relative number of seconds
+        from current time (up to 1 month), or an absolute Unix epoch time.
+        By default, items never expire, though items may be evicted due to
+        memory pressure.  Float values will be rounded up to the nearest
+        whole second.
+      key_prefix: Prefix for to prepend to all keys.
+      min_compress_len: Unimplemented compatibility option.
+
+    Returns:
+      A list of keys whose values were NOT set.  On total success,
+      this list should be empty.
+    """
+    if not isinstance(time, (int, long, float)):
+      raise TypeError('Expiration must be a number.')
+    if time < 0.0:
+      raise ValueError('Expiration must not be negative.')
+
+    request = MemcacheSetRequest()
+    user_key = {}
+    server_keys = []
+    for key, value in mapping.iteritems():
+      server_key = _key_string(key, key_prefix, user_key)
+      stored_value, flags = _validate_encode_value(value, self._do_pickle)
+      server_keys.append(server_key)
+
+      item = request.add_item()
+      item.set_key(server_key)
+      item.set_value(stored_value)
+      item.set_flags(flags)
+      item.set_set_policy(MemcacheSetRequest.SET)
+      item.set_expiration_time(int(math.ceil(time)))
+
+    response = MemcacheSetResponse()
+    try:
+      self._make_sync_call('memcache', 'Set', request, response)
+    except apiproxy_errors.ApplicationError:
+      return False
+
+    assert response.set_status_size() == len(server_keys)
+
+    unset_list = []
+    for server_key, set_status in zip(server_keys, response.set_status_list()):
+      if set_status != MemcacheSetResponse.STORED:
+        unset_list.append(user_key[server_key])
+
+    return unset_list
+
+  def incr(self, key, delta=1):
+    """Atomically increments a key's value.
+
+    Internally, the value is a unsigned 64-bit integer.  Memcache
+    doesn't check 64-bit overflows.  The value, if too large, will
+    wrap around.
+
+    The key must already exist in the cache to be incremented.  To
+    initialize a counter, set() it to the initial value, as an
+    ASCII decimal integer.  Future get()s of the key, post-increment,
+    will still be an ASCII decimal value.
+
+    Args:
+      key: Key to increment.  See Client's docstring for details.
+      delta: Non-negative integer value (int or long) to increment key by,
+        defaulting to 1.
+
+    Returns:
+      New long integer value, or None if key was not in the cache or could not
+      be incremented for any other reason.
+
+    Raises:
+      ValueError: If number is negative.
+      TypeError: If delta isn't an int or long.
+    """
+    return self._incrdecr(key, False, delta)
+
+  def decr(self, key, delta=1):
+    """Atomically decrements a key's value.
+
+    Internally, the value is a unsigned 64-bit integer.  Memcache
+    caps decrementing below zero to zero.
+
+    The key must already exist in the cache to be decremented.  See
+    docs on incr() for details.
+
+    Args:
+      key: Key to decrement.  See Client's docstring for details.
+      delta: Non-negative integer value (int or long) to decrement key by,
+        defaulting to 1.
+
+    Returns:
+      New long integer value, or None if key wasn't in cache and couldn't
+      be decremented.
+
+    Raises:
+      ValueError: If number is negative.
+      TypeError: If delta isn't an int or long.
+    """
+    return self._incrdecr(key, True, delta)
+
+  def _incrdecr(self, key, is_negative, delta):
+    """Increment or decrement a key by a provided delta.
+
+    Args:
+      key: Key to increment or decrement.
+      is_negative: Boolean, if this is a decrement.
+      delta: Non-negative integer amount (int or long) to increment
+        or decrement by.
+
+    Returns:
+      New long integer value, or None on cache miss.
+
+    Raises:
+      ValueError: If delta is negative.
+      TypeError: If delta isn't an int or long.
+    """
+    if not isinstance(delta, (int, long)):
+      raise TypeError('Delta must be an integer or long, received %r' % delta)
+    if delta < 0:
+      raise ValueError('Delta must not be negative.')
+
+    request = MemcacheIncrementRequest()
+    response = MemcacheIncrementResponse()
+    request.set_key(_key_string(key))
+    request.set_delta(delta)
+    if is_negative:
+      request.set_direction(MemcacheIncrementRequest.DECREMENT)
+    else:
+      request.set_direction(MemcacheIncrementRequest.INCREMENT)
+
+    try:
+      self._make_sync_call('memcache', 'Increment', request, response)
+    except apiproxy_errors.ApplicationError:
+      return None
+
+    if response.has_new_value():
+      return response.new_value()
+    return None
+
+
+_CLIENT = None
+
+
+def setup_client(client_obj):
+  """Sets the Client object instance to use for all module-level methods.
+
+  Use this method if you want to have customer persistent_id() or
+  persistent_load() functions associated with your client.
+
+  Args:
+    client_obj: Instance of the memcache.Client object.
+  """
+  global _CLIENT
+  var_dict = globals()
+
+  _CLIENT = client_obj
+  var_dict['set_servers'] = _CLIENT.set_servers
+  var_dict['disconnect_all'] = _CLIENT.disconnect_all
+  var_dict['forget_dead_hosts'] = _CLIENT.forget_dead_hosts
+  var_dict['debuglog'] = _CLIENT.debuglog
+  var_dict['get'] = _CLIENT.get
+  var_dict['get_multi'] = _CLIENT.get_multi
+  var_dict['set'] = _CLIENT.set
+  var_dict['set_multi'] = _CLIENT.set_multi
+  var_dict['add'] = _CLIENT.add
+  var_dict['replace'] = _CLIENT.replace
+  var_dict['delete'] = _CLIENT.delete
+  var_dict['delete_multi'] = _CLIENT.delete_multi
+  var_dict['incr'] = _CLIENT.incr
+  var_dict['decr'] = _CLIENT.decr
+  var_dict['flush_all'] = _CLIENT.flush_all
+  var_dict['get_stats'] = _CLIENT.get_stats
+
+
+setup_client(Client())