Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions diskcache/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ def full_name(func):
return func.__module__ + '.' + func.__qualname__



def _reject_bool_numeric(value, name):
"""Raise TypeError if value is bool (bool subclasses int/float)."""
if isinstance(value, bool):
raise TypeError(
'%s must be a number, not bool (got %r; bool subclasses int so '
'%r would silently become %d)' % (name, value, value, int(value))
)



class Constant(tuple):
"""Pretty display of immutable constant."""

Expand Down Expand Up @@ -587,6 +598,8 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings):
# Close and re-open database connection with given timeout.

self.close()
# bool subclasses int; timeout=True would silently become 1s
_reject_bool_numeric(timeout, 'timeout')
self._timeout = timeout
self._sql # pylint: disable=pointless-statement

Expand Down Expand Up @@ -768,6 +781,10 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False):
"""
now = time.time()
db_key, raw = self._disk.put(key)
# bool subclasses int; expire=True would silently become 1s TTL;
# expire=False would become 0 (immediate expiry)
if expire is not None:
_reject_bool_numeric(expire, 'expire')
expire_time = None if expire is None else now + expire
size, mode, filename, db_value = self._disk.store(value, read, key=key)
columns = (expire_time, tag, size, mode, filename, db_value)
Expand Down Expand Up @@ -940,6 +957,10 @@ def touch(self, key, expire=None, retry=False):
"""
now = time.time()
db_key, raw = self._disk.put(key)
# bool subclasses int; expire=True would silently become 1s TTL;
# expire=False would become 0 (immediate expiry)
if expire is not None:
_reject_bool_numeric(expire, 'expire')
expire_time = None if expire is None else now + expire

with self._transact(retry) as (sql, _):
Expand Down Expand Up @@ -988,6 +1009,10 @@ def add(self, key, value, expire=None, read=False, tag=None, retry=False):
"""
now = time.time()
db_key, raw = self._disk.put(key)
# bool subclasses int; expire=True would silently become 1s TTL;
# expire=False would become 0 (immediate expiry)
if expire is not None:
_reject_bool_numeric(expire, 'expire')
expire_time = None if expire is None else now + expire
size, mode, filename, db_value = self._disk.store(value, read, key=key)
columns = (expire_time, tag, size, mode, filename, db_value)
Expand Down Expand Up @@ -1445,6 +1470,10 @@ def push(

now = time.time()
raw = True
# bool subclasses int; expire=True would silently become 1s TTL;
# expire=False would become 0 (immediate expiry)
if expire is not None:
_reject_bool_numeric(expire, 'expire')
expire_time = None if expire is None else now + expire
size, mode, filename, db_value = self._disk.store(value, read)
columns = (expire_time, tag, size, mode, filename, db_value)
Expand Down
44 changes: 44 additions & 0 deletions tests/test_bool_expire_timeout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""bool subclasses int; expire=True must not silently become 1s TTL."""
import shutil

import pytest

import diskcache as dc


@pytest.fixture
def dcache():
with dc.Cache() as cache:
yield cache
shutil.rmtree(cache.directory, ignore_errors=True)


@pytest.mark.parametrize("value", [True, False])
def test_cache_timeout_rejects_bool(value, tmpdir):
with pytest.raises(TypeError, match="timeout"):
dc.Cache(str(tmpdir), timeout=value)


@pytest.mark.parametrize("value", [True, False])
def test_set_expire_rejects_bool(value, dcache):
with pytest.raises(TypeError, match="expire"):
dcache.set("k", "v", expire=value)


@pytest.mark.parametrize("value", [True, False])
def test_add_expire_rejects_bool(value, dcache):
with pytest.raises(TypeError, match="expire"):
dcache.add("k", "v", expire=value)


@pytest.mark.parametrize("value", [True, False])
def test_touch_expire_rejects_bool(value, dcache):
dcache.set("k", "v")
with pytest.raises(TypeError, match="expire"):
dcache.touch("k", expire=value)


def test_expire_still_accepts_number(dcache):
assert dcache.set("k", "v", expire=5) is True
assert dcache.get("k") == "v"
assert dcache.touch("k", expire=10) is True