Does not mock time for TTLCache
irusland opened this issue · 5 comments
Hey, I've recently encountered the problem with freezing the time in TTLCache
Steps to reproduce
import time
from datetime import timedelta
from cachetools import TTLCache
from freezegun import freeze_time
with freeze_time() as frozen_datetime:
ttl = TTLCache(
maxsize=10, ttl=1,
# timer=time.monotonic, # uncomment this to pass assertion
)
ttl.update({'1': 1})
assert ttl.keys()
frozen_datetime.tick(timedelta(seconds=10000))
assert not ttl.keys()
The problem with that is when use reference as a function parameter it caches timer=time.monotonic in defaults so freeze gun does not override it
Is there a way to overcome this?
Ive come up with the solution as overriding the standard ttlcache like so
import time
from cachetools import TTLCache as TTLCacheBase
class TTLCache(TTLCacheBase):
def __init__(self, maxsize, ttl, timer=None, getsizeof=None):
if timer is None:
timer = time.monotonic
super().__init__(maxsize=maxsize, ttl=ttl, timer=timer, getsizeof=getsizeof)
Same:
In [1]: from freezegun import freeze_time
In [2]: from datetime import datetime, timedelta
In [3]: from cachetools.func import ttl_cache
In [4]: @ttl_cache(ttl=100)
...: def now():
...: return datetime.now()
In [5]: import time
In [6]: timer = time.monotonic
In [7]: print(timer())
...: print(now())
...: with freeze_time(timedelta(days=4), tick=True):
...: print(timer())
...: print(now())
...: print(timer())
61.09706311
2022-11-17 12:51:30.917532
1669027890.946961
2022-11-17 12:51:30.917532
1669027890.947039
+1 I'm running into this too. It's making ttl_cache
impossible to test elegantly.
to solve this cachetools would have to stop memoizing pre-patching time functions in the factories
I hit this same issue today, and found that a relatively simple fix was to create a timer function that returns the result of time.monotonic()
- this way, freezegun's patching seems to work as expected
def mytimer():
return time.monotonic()
@cached(cache=TTLCache(maxsize=128, ttl=300, timer=mytimer))
def my_cached_function():
etc...
Once I did that all the tests that were supposed to pass suddenly passed!
Looking at @POD666's test, I turned it into a script as follows:
from freezegun import freeze_time
from datetime import datetime, timedelta
from cachetools.func import ttl_cache
import time
def mytimer():
return time.monotonic()
@ttl_cache(ttl=100, timer=mytimer)
def now():
return datetime.now()
timer = time.monotonic
print(timer())
print(now())
with freeze_time(timedelta(days=4), tick=True):
print(timer())
print(now())
print(timer())
and get
4826002.177211833
2024-07-16 15:54:07.326647
1721454847.342787
2024-07-20 05:54:07.342823
1721454847.342848
as expected (the main change is that TTLCache timer goes four days into the future, causing the ttl cache to expire, so the second call to now() is correct) - before adding the timer function, I got the same result as the original comment - the result being the same value