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
TTLCache with "absolute" expiration times #219
Comments
You could write a custom timer for this somehow like the def my_timer():
now = datetime.now()
# floor to minute
dt = datetime(year=now.year, month=now.month, day=now.day, hour=now.hour, minute=now.minute)
# floor to hour
dt = datetime(year=now.year, month=now.month, day=now.day, hour=now.hour)
# even for a specific time
target_minute, target_second = 15, 16
dt = datetime(year=now.year, month=now.month, day=now.day, hour=now.hour, minute=target_minute, second=target_second)
if dt > now:
dt -= timedelta(hours=1)
return dt.timestamp()
cache = TTLCache(5, ttl=3600 - 1E-5, timer=my_timer) # expire at HH:15:16 for every hour |
Thanks for the quick answer! I'm not sure i understand what the expectation for "timer" to return is? Also, the above code (i've tried the "floor to minute") doesn't seem to work properly ... the items still expire only after their full minute of expiration - even though my timer-function returns a full minute |
It seems that we should add a small number to the cachetools/src/cachetools/ttl.py Lines 166 to 171 in 40d2710
import time
from datetime import datetime, timedelta
from cachetools.func import ttl_cache
def my_timer():
now = datetime.now()
# floor to minute
dt = datetime(year=now.year, month=now.month, day=now.day, hour=now.hour, minute=now.minute)
print('Register as {}'.format(dt))
return dt.timestamp()
@ttl_cache(ttl=(60 - 1E-5), timer=my_timer) # ttl is slightly smaller than 60
def now():
return datetime.now()
for i in range(30):
print('Function output: {}'.format(now()))
print('Sleep 5 seconds')
time.sleep(5)
print() And I got correct results as expected: Results...
|
I am not sure if #220 is really necessary. My solution now is the following (naming "stolen" from your above comment) from cachetools.ttl import TTLCache
class PeriodicCache(TTLCache):
"""
Special cache that expires at "straight" times
A timer with ttl of 3600 (1h) will expire at every full hour (:00).
"""
def __init__(self, maxsize, ttl, getsizeof=None):
def local_timer():
ts = datetime.now(timezone.utc).timestamp()
offset = (ts % ttl)
return ts - offset
# Init with smlight offset
super().__init__(maxsize=maxsize, ttl=ttl-1e-5, timer=local_timer, getsizeof=getsizeof) I've setup a few tests for this and it seems to work reliably - as long as a "repeating" pattern is used for ttl (60, 120, 30, 3600, ...) - as otherwise the repeating-logic will result in "shifting" logic - which means you can't really predict when it'll expire. |
For me, I prefer to use the import time
from datetime import datetime, timedelta
from .ttl import TTLCache
class PeriodicCache(TTLCache):
def __init__(self, maxsize, period, reference=0, timer=time.perf_counter, getsizeof=None):
self.__period = period
self.__reference = reference
self.__external_timer = timer
TTLCache.__init__(self, maxsize, ttl=period, timer=self.__periodic_timer,
getsizeof=getsizeof, exclusive=True)
def __periodic_timer(self):
time = self.__external_timer()
offset = (time - self.__reference) % self.__period
return time - offset
cache_full_minute_proc_time = PeriodicCache(128, period=60.0)
cache_full_hour_proc_time = PeriodicCache(128, period=3600.0)
cache_half_hour_proc_time = PeriodicCache(128, period=3600.0, reference=1800.0)
cache_full_minute_utc_time = PeriodicCache(128, period=timedelta(minutes=1),
reference=datetime(year=2000, month=1, day=1),
timer=datetime.now)
cache_full_hour_utc_time = PeriodicCache(128, period=timedelta(hours=1),
reference=datetime(year=2000, month=1, day=1),
timer=datetime.now)
cache_half_hour_utc_time = PeriodicCache(128, period=timedelta(hours=1),
reference=datetime(year=2000, month=1, day=1, hour=0, minute=30),
timer=datetime.now) |
@xmatthias: Back to your original request...
It would be interesting to know why you'd need such a thing (knowing the use case might help e.g. find alternative solutions), but no, such a thing does not exist in
Well, the docs (https://cachetools.readthedocs.io/en/stable/#cachetools.TTLCache) could surely be improved in this respect (see also #216), but basically it returns the current time, as used by the cache. This does not necessarily have to be wall-clock time, for example in the I still don't fully understand your use case, but if you want to expire items at every full hour, maybe you could use a timer with 1-hour-resolution, e.g.
and specify the |
As commented in #219 (comment) and #220, The actual period (or ttl) in the following case is 2 hour rather than desired 1 hour: cache = TTLCache(128, ttl=1, timer=lambda : int(time.time() / 3600)) # period is 2 hour If we need an 1 hour cache: cache = TTLCache(128, ttl=0.9, timer=lambda : int(time.time() / 3600)) # period is 1 hour |
My usecase is a financial one - think Candlestick charts of different resolutions. Obviously this applies to 1h candles, 30m candles, 5m, or even 1d candles (candle being specified timeframe that elapses, and is always given by "clock time".
Without the PR #220, this will result in 2h durations - which is clearly wrong.
Can you highlight / point out some technical reasons (not "because i designed it the other way) to not subclass Derriving from |
That's as good a reason as any in my book. Trying to twist and bend something that just wasn't designed with your particular use case in mind is usually more trouble than it's worth. Go and look for an alternative solution!
You'll find enough resources online that explain the issues with this kind of "implementation inheritance", and why you should change your mind about this.
I agree, the ttl handling is not really intuitive when using small integers. However, as @XuehaiPan pointed out above, this is easy to work around even without PR #220. Back to your use case: If I understand correctly, you don't even need a TTL, since all cache entries will expire at every full hour? Then why use a
|
You should change your mind about how you handle issues that try to be constructive and improve your project. To me, this whole answer sounds like a pretty frustrated maintainer.
I understand you didn't intend it to be used this way - but apparently we hit a weak spot (maybe you're suspecting issues in TTL cache's implementation?) - but that's no reason to get defensive and trying to attack the way people are trying to use your tool (especially if the 2nd part of the answer contains a very similar approch). I'm sure i can find enough resources online that also explain that reimplementing / copy/pasting 90% of good code is also an even worse approach.
Now this is very inconsequent and contradicts what you said above. Why is inheriting from This confirms my suspicion that you're seeing some problems with |
Well, let me assure you that I'm not frustrated, and still happy to maintain this. Which does not mean I'll happily embrace each and every feature/change request or PR that doesn't make my life easier. That said, I didn't mean to "attack" anybody. AFAICS I just stated that I think your approach is wrong, and I tried to come up with an alternative solution. You're free to use that or whatever you come up with yourself, of course. Just don't waste your and my time any more with this, thank you. Let me also assure you that you didn't "hit a weak spot" - there are no issues with the Finally, use of inheritance in the example above was marked as "for exposition only". I would probably use a separate accessor function, but to keep this short and since you seem to like inheritance, I tried to do you a favor. Case closed. |
I need to have a TTLCache that has absolute expiration times.
This means, i have a cache which expires items every full hour (for example) - or always at midnight.
Currently, TTL seems to only be possible for relative values (200s from now, 1000s from now) - but not "until the clock hits :00 next time" - so the insert time will determine how long it'll live:
In the above sample, item 'a' will live for 1 hour - but it might expire at 18:30, or 18:34, (exactly one hour after i inserted it). I'd however like it to expire at 18:00, 19:00, 20:00 (independently of the insertion time).
I don't mind creating a subclass of TTLCache for this myself, but TTLCache seems pretty involved / complex and not well documented (what does what) - so i'm currently not sure where to start.
I'll potentially need a different timer - but i'm not sure that will suffice my requirements (unfortunately, this is not pretty well documented).
I could also work with it by somehow modifying the "expire" (all items should expire at the same moment - when the clock hits 11:00, 12:00, ... ) - so i think calling
.expire()
after insertion "might" work (although i'm not sure that will suffice my needs).The math of the expiration is pretty easy (to get to full hours) - but i miss understanding where i should plug this in ...
The text was updated successfully, but these errors were encountered: