fix(cache): distinguish callables in the shared EMS cache

Include the function object in cache keys so methods of one interpolator cannot reuse a probability as a power value. Cover both call orders, keyword arguments, cache hits and separate closures with identical qualified names.
This commit is contained in:
Andreas
2026-09-16 18:24:45 +02:00
parent 0251af0bb4
commit be184a6259
2 changed files with 52 additions and 0 deletions
+3
View File
@@ -228,6 +228,9 @@ def cache_energy_management(
cached_wrapper = cachebox.cached( cached_wrapper = cachebox.cached(
cache=CacheEnergyManagementStore().cache, cache=CacheEnergyManagementStore().cache,
# This store is shared by all decorated callables, including methods
# on the same instance. Arguments alone cannot identify their results.
key_maker=lambda *args, **kwargs: cachebox.make_key(func, *args, **kwargs),
callback=cache_energy_management_store_callback, callback=cache_energy_management_store_callback,
)(wrapper) )(wrapper)
+49
View File
@@ -88,6 +88,55 @@ class TestCacheUntilUpdateDecorators:
assert CacheEnergyManagementStore.hit_count == 1 assert CacheEnergyManagementStore.hit_count == 1
assert result1 == result2 assert result1 == result2
@pytest.mark.parametrize("reverse", [False, True])
@pytest.mark.parametrize("use_kwargs", [False, True])
def test_methods_with_identical_arguments_keep_separate_results(
self, cache_energy_management_store, reverse, use_kwargs
):
calls = []
class Model:
@cache_energy_management
def fraction(self, value):
calls.append("fraction")
return value / 1000
@cache_energy_management
def energy(self, value):
calls.append("energy")
return value * 1000
model = Model()
cases = [(model.fraction, 0.005), (model.energy, 5000)]
if reverse:
cases.reverse()
for _ in range(2):
for method, expected in cases:
result = method(value=5) if use_kwargs else method(5)
assert result == expected
assert sorted(calls) == ["energy", "fraction"]
assert CacheEnergyManagementStore.miss_count == 2
assert CacheEnergyManagementStore.hit_count == 2
def test_distinct_closures_with_same_name_do_not_share_results(
self, cache_energy_management_store
):
def make_function(factor):
@cache_energy_management
def compute(value):
return value * factor
return compute
double = make_function(2)
triple = make_function(3)
assert double.__qualname__ == triple.__qualname__
assert double(4) == 8
assert triple(4) == 12
assert double(4) == 8
assert triple(4) == 12
assert CacheEnergyManagementStore.miss_count == 2
assert CacheEnergyManagementStore.hit_count == 2
def test_cache_energy_management(self, cache_energy_management_store): def test_cache_energy_management(self, cache_energy_management_store):
"""Test that cache_energy_management caches function results.""" """Test that cache_energy_management caches function results."""