diff --git a/diskcache/recipes.py b/diskcache/recipes.py index babb68f..dc41efa 100644 --- a/diskcache/recipes.py +++ b/diskcache/recipes.py @@ -266,6 +266,9 @@ def throttle( Assumes keys will not be evicted. Set the eviction policy to 'none' on the cache to guarantee the keys are not evicted. + The token capacity `count` must be finite and at least one. The refill + period `seconds` must be finite and greater than zero. + >>> import diskcache, time >>> cache = diskcache.Cache() >>> count = 0 @@ -282,6 +285,10 @@ def throttle( """ def decorator(func): + if not math.isfinite(count) or count < 1: + raise ValueError('count must be finite and at least one') + if not math.isfinite(seconds) or seconds <= 0: + raise ValueError('seconds must be finite and greater than zero') rate = count / float(seconds) key = full_name(func) if name is None else name now = time_func() diff --git a/tests/test_recipes.py b/tests/test_recipes.py index ae74459..42c0a4e 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -104,3 +104,23 @@ def worker(num): while (time.time() - start) < 1: worker(100) assert state['num'] > 0 + + +@pytest.mark.parametrize('count', [0, -1, 0.5, float('nan'), float('inf')]) +def test_throttle_rejects_invalid_capacity(cache, count): + with pytest.raises(ValueError, match='count'): + dc.throttle(cache, count, 1, name='invalid')(lambda: None) + assert 'invalid' not in cache + + +@pytest.mark.parametrize('seconds', [0, -1, float('nan'), float('inf')]) +def test_throttle_rejects_invalid_period(cache, seconds): + with pytest.raises(ValueError, match='seconds'): + dc.throttle(cache, 1, seconds, name='invalid')(lambda: None) + assert 'invalid' not in cache + + +@pytest.mark.parametrize('count, seconds', [(1, 0.25), (1.5, 2), (2, 1)]) +def test_throttle_valid_capacity_and_period(cache, count, seconds): + function = dc.throttle(cache, count, seconds)(lambda: 'result') + assert function() == 'result'