--- /dev/null
+from hashlib import md5
+
+CONTROL_CHARACTERS = set([chr(i) for i in range(0,33)])
+CONTROL_CHARACTERS.add(chr(127))
+
+def sanitize_memcached_key(key, max_length=250):
+ """ Removes control characters and ensures that key will
+ not hit the memcached key length limit by replacing
+ the key tail with md5 hash if key is too long.
+ """
+ key = ''.join([c for c in key if c not in CONTROL_CHARACTERS])
+ if len(key) > max_length:
+ hash = md5(key).hexdigest()
+ key = key[:max_length-33]+'-'+hash
+ return key
+
+def _args_to_unicode(args, kwargs):
+ key = ""
+ if args:
+ key += unicode(args)
+ if kwargs:
+ key += unicode(kwargs)
+ return key
+
+
+def _func_type(func):
+ """ returns if callable is a function, method or a classmethod """
+ argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
+ if len(argnames) > 0:
+ if argnames[0] == 'self':
+ return 'method'
+ if argnames[0] == 'cls':
+ return 'classmethod'
+ return 'function'
+
+
+def _func_info(func, args):
+ ''' introspect function's or method's full name.
+ Returns a tuple (name, normalized_args,) with
+ 'cls' and 'self' removed from normalized_args '''
+
+ func_type = _func_type(func)
+
+ if func_type == 'function':
+ return ".".join([func.__module__, func.__name__]), args
+
+ class_name = args[0].__class__.__name__
+ if func_type == 'classmethod':
+ class_name = args[0].__name__
+
+ return ".".join([func.__module__, class_name, func.__name__]), args[1:]
+
+
+def _cache_key(func_name, func_type, args, kwargs):
+ """ Construct readable cache key """
+ if func_type == 'function':
+ args_string = _args_to_unicode(args, kwargs)
+ else:
+ args_string = _args_to_unicode(args[1:], kwargs)
+ return sanitize_memcached_key('[cached]%s(%s)' % (func_name, args_string,))