I built a cache that invalidates based on expiration time but then I realized that ExpiringCaches.jl (and few others) exist :-p
Just capturing what I did:
"""
TimedCache
A time-based cache. To construct a new cache, provie the types for key and value
and a retention period. It is lazy - when a cache entry expires, it is not removed
until the cache is accessed again.
"""
struct TimedCache{K,V}
data::Dict{K,V}
registry::Dict{K,DateTime}
retention::TimePeriod
end
function TimedCache{K,V}(retention::TimePeriod) where {K,V}
return TimedCache(Dict{K,V}(), Dict{K,DateTime}(), retention)
end
function Base.show(io::IO, c::TimedCache{K,V}) where {K,V}
print(io, "TimedCache{", K, ",", V, "}(size=", length(c.data), ", retention=", c.retention, ")")
end
for f in (:length, :keys, :values)
@eval function Base.$f(c::TimedCache)
clean(c)
$f(c.data)
end
end
function Base.getindex(c::TimedCache{K,V}, key::K) where {K,V}
clean(c)
return c.data[key]
end
function Base.setindex!(c::TimedCache{K,V}, val::V, key::K) where {K,V}
clean(c)
c.data[key] = val
c.registry[key] = now()
return val
end
"""
Base.get!(f::Function, c::TimedCache, key)
Return the value stored in the cache for the given `key`. If `key` is not found in the
cache or if the entry has already expired according to the retention specified in `c`,
then the function `f` is executed and the new value is stored in cache.
"""
function Base.get!(f::Function, c::TimedCache, key)
clean(c)
return get!(c.data, key) do
value = f()
c.data[key] = value
c.registry[key] = now()
value
end
end
"""
clean(::TimedCache)
Clean the cache by removing expired entries.
"""
function clean(c::TimedCache)
current_time = now()
stale_keys = [k for (k, dt) in c.registry if dt + c.retention < current_time]
foreach(k -> delete!(c.data, k), stale_keys)
foreach(k -> delete!(c.registry, k), stale_keys)
end
Test:
using Test
using Dates
using HoJBot: TimedCache
@testset "Cache" begin
# Test get!
retention = 1
tc = TimedCache{Int,String}(Second(retention))
get!(tc, 1) do; "hey"; end
@test length(tc) == 1
sleep(retention)
@test length(tc) == 0
# Test keys, values, getindex, setindex!
tc[1] = "hey"
@test tc[1] == "hey"
@test collect(keys(tc)) == [1]
@test collect(values(tc)) == ["hey"]
sleep(retention)
@test_throws KeyError tc[1]
end