memcached memoization function decorator.

    The wrapped function is expected to return a value that is stored to a
    memcached server, first translated by `to_cache` if provided. In the event
    of a cache hit, the data is translated by `from_cache` if provided, before
    being returned. If you do not want a result to be cached, wrap the return
    value of your function in a `DoNotCache` object.

    Example:

        @memcached('127.0.0.1:11211')
        def _listdir(path):
            return os.path.listdir(path)

    Note:
        If using the default key function, ensure that repr() is implemented on
        all your arguments and that they are hashable.

    Note:
        `from_cache` and `to_cache` both accept the value as first parameter,
        then the target function's arguments follow.

    Args:
        servers (str or list of str): memcached server uri(s), eg '127.0.0.1:11211'.
            This arg can be None also, in which case memcaching is disabled.
        key (callable, optional): Function that, given the target function's args,
            returns the string key to use in memcached.
        from_cache (callable, optional): If provided, and a cache hit occurs, the
            cached value will be translated by this function before being returned.
        to_cache (callable, optional): If provided, and a cache miss occurs, the
            function's return value will be translated by this function before
            being cached.
        time (int): Tells memcached the time which this value should expire, either
            as a delta number of seconds, or an absolute unix time-since-the-epoch
            value. See the memcached protocol docs section "Storage Commands"
            for more info on <exptime>. We default to 0 == cache forever.
        min_compress_len (int): The threshold length to kick in auto-compression
            of the value using the zlib.compress() routine. If the value being cached is
            a string, then the length of the string is measured, else if the value is an
            object, then the length of the pickle result is measured. If the resulting
            attempt at compression yeilds a larger string than the input, then it is
            discarded. For backwards compatability, this parameter defaults to 0,
            indicating don't ever try to compress.
        debug (bool): If True, memcache keys are kept human readable, so you can
            read them if running a foreground memcached proc with 'memcached -vv'.
            However this increases chances of key clashes so should not be left
            turned on.

def memcached(servers, key=None, from_cache=None, to_cache=None, time=0,
              min_compress_len=0, debug=False):
    """memcached memoization function decorator.

    The wrapped function is expected to return a value that is stored to a
    memcached server, first translated by `to_cache` if provided. In the event
    of a cache hit, the data is translated by `from_cache` if provided, before
    being returned. If you do not want a result to be cached, wrap the return
    value of your function in a `DoNotCache` object.

    Example:

        @memcached('127.0.0.1:11211')
        def _listdir(path):
            return os.path.listdir(path)

    Note:
        If using the default key function, ensure that repr() is implemented on
        all your arguments and that they are hashable.

    Note:
        `from_cache` and `to_cache` both accept the value as first parameter,
        then the target function's arguments follow.

    Args:
        servers (str or list of str): memcached server uri(s), eg '127.0.0.1:11211'.
            This arg can be None also, in which case memcaching is disabled.
        key (callable, optional): Function that, given the target function's args,
            returns the string key to use in memcached.
        from_cache (callable, optional): If provided, and a cache hit occurs, the
            cached value will be translated by this function before being returned.
        to_cache (callable, optional): If provided, and a cache miss occurs, the
            function's return value will be translated by this function before
            being cached.
        time (int): Tells memcached the time which this value should expire, either
            as a delta number of seconds, or an absolute unix time-since-the-epoch
            value. See the memcached protocol docs section "Storage Commands"
            for more info on <exptime>. We default to 0 == cache forever.
        min_compress_len (int): The threshold length to kick in auto-compression
            of the value using the zlib.compress() routine. If the value being cached is
            a string, then the length of the string is measured, else if the value is an
            object, then the length of the pickle result is measured. If the resulting
            attempt at compression yeilds a larger string than the input, then it is
            discarded. For backwards compatability, this parameter defaults to 0,
            indicating don't ever try to compress.
        debug (bool): If True, memcache keys are kept human readable, so you can
            read them if running a foreground memcached proc with 'memcached -vv'.
            However this increases chances of key clashes so should not be left
            turned on.
    """
    def default_key(func, *nargs, **kwargs):
        parts = [func.__module__]

        argnames = getargspec(func).args
        if argnames:
            if argnames[0] == "cls":
                cls_ = nargs[0]
                parts.append(cls_.__name__)
                nargs = nargs[1:]
            elif argnames[0] == "self":
                cls_ = nargs[0].__class__
                parts.append(cls_.__name__)
                nargs = nargs[1:]

        parts.append(func.__name__)

        value = ('.'.join(parts), nargs, tuple(sorted(kwargs.items())))
        # make sure key is hashable. We don't strictly need it to be, but this
        # is a way of hopefully avoiding object types that are not ordered (these
        # would give an unreliable key). If you need to key on unhashable args,
        # you should provide your own `key` functor.
        _ = hash(value)
        return repr(value)

    def identity(value, *nargs, **kwargs):
        return value

    from_cache = from_cache or identity
    to_cache = to_cache or identity

    def decorator(func):
        if servers:
            def wrapper(*nargs, **kwargs):
                with memcached_client(servers, debug=debug) as client:
                    if key:
                        cache_key = key(*nargs, **kwargs)
                    else:
                        cache_key = default_key(func, *nargs, **kwargs)

                    # get
                    result = client.get(cache_key)
                    if result is not client.miss:
                        return from_cache(result, *nargs, **kwargs)

                    # cache miss - run target function
                    result = func(*nargs, **kwargs)
                    if isinstance(result, DoNotCache):
                        return result.result

                    # store
                    cache_result = to_cache(result, *nargs, **kwargs)
                    client.set(key=cache_key,
                               val=cache_result,
                               time=time,
                               min_compress_len=min_compress_len)
                    return result
        else:
            def wrapper(*nargs, **kwargs):
                result = func(*nargs, **kwargs)
                if isinstance(result, DoNotCache):
                    return result.result
                return result

        def forget():
            """Forget entries in the cache.

            Note that this does not delete entries from a memcached server - that
            would be slow and error-prone. Calling this function only ensures
            that entries set by the current process will no longer be seen during
            this process.
            """
            with memcached_client(servers, debug=debug) as client:
                client.flush()

        wrapper.forget = forget
        wrapper.__wrapped__ = func
        return update_wrapper(wrapper, func)
    return decorator