def lru_cache_key(key_func, maxsize=1024):
    """Like functools.lru_cache, but takes a custom key function,
    as seen in sorted(key=func).

    Useful for non-hashable arguments which have a known hashable equivalent (e.g. sets, lists),
    or mutable objects where only immutable fields might be used
    (e.g. User, where only username affects output).

    For safety: Cached results should always be immutable,
    such as using `frozenset` instead of mutable `set`.

    Example:

        @lru_cache_key(lambda user: user.name)
        def func_user(user):
            # output only varies by name

    Args:
        key (callable):
            Should have the same signature as the decorated function.
            Returns a hashable key to use in the cache
        maxsize (int):
            The maximum size of the cache.
    """

    def cache_func(func):
        cache = LRUCache(maxsize=maxsize)

        # the actual decorated function:
        @wraps(func)
        def cached(*args, **kwargs):
            cache_key = key_func(*args, **kwargs)
            if cache_key in cache:
                # cache hit
                return cache[cache_key]
            else:
                # cache miss, call function and cache result
                result = func(*args, **kwargs)
                if isinstance(result, DoNotCache):
                    # DoNotCache prevents caching
                    result = result.result
                else:
                    cache[cache_key] = result
            return result

        return cached

    return cache_func
Like functools.lru_cache, but takes a custom key function,
as seen in sorted(key=func).

Useful for non-hashable arguments which have a known hashable equivalent (e.g. sets, lists),
or mutable objects where only immutable fields might be used
(e.g. User, where only username affects output).

For safety: Cached results should always be immutable,
such as using `frozenset` instead of mutable `set`.

Example:

@lru_cache_key(lambda user: user.name)
def func_user(user):
# output only varies by name

Args:
key (callable):
Should have the same signature as the decorated function.
Returns a hashable key to use in the cache
maxsize (int):
The maximum size of the cache.
jupyterhub/_memoize.py