Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalidating cached values in "cachemethod" #218

Open
ShivKJ opened this issue Aug 24, 2021 · 10 comments
Open

Invalidating cached values in "cachemethod" #218

ShivKJ opened this issue Aug 24, 2021 · 10 comments

Comments

@ShivKJ
Copy link

ShivKJ commented Aug 24, 2021

Hi,

Is there a way to update/remove cache value returned by cachemethod?

For example, in the below code,

import operator as op
from dataclasses import dataclass, field

from cachetools import TTLCache, cachedmethod


@dataclass
class A:
    cache: TTLCache = field(init=False)

    def __post_init__(self):
        self.cache = TTLCache(maxsize=32, ttl=2 * 3600)

    @cachedmethod(cache=op.attrgetter('cache'))
    def compute(self, i):
        print('value of i', i)
        return i * 2

Suppose, I want to remove cached value for a given i from cache, then it is not possible to do directly.

Meanwhile, I learned about hashkey function in keys module. But it is not exposed in cachetools __init__, so can not be imported (of course there are hacks around it). Using this, we can updated cached value.


So, I think there could be two solutions to it,

  1. As cachemethod wraps a method to enable caching feature in it, so apart from the caching feature, it can add functionality to invalidate. For example,
a = A()

print(a.compute(10))

a.compute.remove(10) # remove can have signature *args, **kwargs. Instead of word `remove`, a more better function name can be thought
  1. Exposing keys module in cachetools __init__.py.
from cachetools.keys import hashkey

a = A()

i = 10
print(a.compute(i))

del a.cache[hashkey(i)]

I think approach 1 would be more user friendly.

@tkem
Copy link
Owner

tkem commented Sep 16, 2021

There is an example for removing individual cache items using hashkey in the @cached decorator docs: https://cachetools.readthedocs.io/en/stable/#cachetools.cached
Maybe I should state explicitly that this work similar for @cachedmethod.
However, I agree that it is quite cumbersome to use as it is. OTOH, my usual advice for anybody trying to do fancy stuff with the @cached or @cachedmethod decorators is: Don't. Use the decorators for the most simple use cases, for which they were designed. If you think you need to do something that requires more control of the underlying cache object, just access/query the cache object directly.

@tkem
Copy link
Owner

tkem commented Sep 26, 2021

AFAICS this is also quite similar to #176.

@infohash
Copy link

infohash commented Jun 14, 2022

I am in similar situation. Is there any way to delete a specific cache key from TTLCache. The key in TTLCache is stored as tuple.

TTLCache({(<class 'cachetools.keys._HashedTuple'>, 'token', 'd4e498d636e76dd3ea19fb727dc8f828'): TokenClass object at 0x000001F939670250>}

How do I reconstruct this cache key to pop its value?

token_function.cache.pop(token_function.cache_key(?), None)

When I tried to reconstruct the tuple to pass it as a key, it raises KeyError.

token_function.cache.pop(token_function.cache_key('token', 'd4e498d636e76dd3ea19fb727dc8f828'), None)

I'm not able to reconstruct the first element of this tuple <class 'cachetools.keys._HashedTuple'.

@tkem
Copy link
Owner

tkem commented Jun 15, 2022

@infohash: Are you talking about the @cached or the @cachedmethod decorator?
@cachedmethod properties are still considered somewhat "experimental", and are therefore not properly documented yet.
For @cached, the arguments to the cache_key function are simply the same as for the wrapped function, i.e. not a tuple:

from cachetools import TTLCache, cached

cache = TTLCache(maxsize=1024, ttl=600)

@cached(cache=cache)
def token_function(token, id):
    return (token, id)

print(token_function('token', 'd4e498d636e76dd3ea19fb727dc8f828'))
print(cache)
token_function.cache.pop(token_function.cache_key('token', 'd4e498d636e76dd3ea19fb727dc8f828'), None)
print(cache)

@infohash
Copy link

infohash commented Jun 15, 2022

I am using @cachedmethod because the method that I am applying cache on is an instance method of a class and I initialize TTLCache inside the init method.

from cachetools import TTLCache, cachedmethod


class TokenClass:
    def __init__(self):
        self.cache = TTLCache(maxsize=128, ttl=300)

    @cachedmethod(cache=lambda self: self.cache)
    def token_function(self, token):
        ...

It works fine for storing the cache, the problem happens when I try to remove a specific key from it. I have changed my design so I don't need to pop a key from TTLCache anymore. But it's something you can check because it almost works.

@tkem
Copy link
Owner

tkem commented Jun 15, 2022

So, the token argument is actually a tuple?
Bear in mind that cache in @cachedmethod is actually a method itself (i.e. needs passing self), and the cache_key gets passed self as its first argument:

from cachetools import TTLCache, cachedmethod

class TokenClass:
    def __init__(self):
        self.cache = TTLCache(maxsize=128, ttl=300)

    @cachedmethod(cache=lambda self: self.cache)
    def token_function(self, token):
        return token

tc = TokenClass()
print(tc.token_function(('token', 'd4e498d636e76dd3ea19fb727dc8f828')))
print(tc.token_function.cache(tc))
tc.token_function.cache(tc).pop(tc.token_function.cache_key(tc, ('token', 'd4e498d636e76dd3ea19fb727dc8f828')), None)
print(tc.token_function.cache(tc))

It's cumbersome, and I'm not too happy with it myself, so it will probably remain an experimental, undocumented feature until someone comes up with something better (or somebody provides sensible documentation for this)...

@infohash
Copy link

@tkem I have solved it finally. So if the method is called with the keyword argument, the cache_key must also take the keyword argument.

tc.token_function.cache(tc).pop(tc.token_function.cache_key(tc, token=d4e498d636e76dd3ea19fb727dc8f828'), None)

@tkem
Copy link
Owner

tkem commented Jul 10, 2022

@infohash: Glad you solved it. Yes, documentation should (and eventually, will) be added for this.

@andrii-korotkov
Copy link

@tkem, @ShivKJ, I've implemented a simple cache_clear_entry method for both cached and cachedmethod. See https://pastebin.com/C5m73XAE for the diff. I've added some tests and made sure they pass. Please, share your feedback. If this looks good, please give me a permission to push a branch. If not, maybe just apply a change yourself. Thank you.

@kuraga
Copy link
Contributor

kuraga commented Feb 26, 2024

@tkem, @ShivKJ, I've implemented a simple cache_clear_entry method for both cached and cachedmethod. See https://pastebin.com/C5m73XAE for the diff. I've added some tests and made sure they pass. Please, share your feedback. If this looks good, please give me a permission to push a branch. If not, maybe just apply a change yourself. Thank you.

@tkem, @ShivKJ, ping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants