From 4569d2a0fb9c883469b4024d022908d982c3b767 Mon Sep 17 00:00:00 2001 From: hikmetba-bit Date: Wed, 16 Sep 2026 20:43:02 +0300 Subject: [PATCH] Tag cache directories with CACHEDIR.TAG Fixes #352. Writes a standard CACHEDIR.TAG file (https://bford.info/cachedir/) into each Cache directory on init, so backup tools (rsync --cvs-exclude, tar --exclude-caches, etc.) can skip disposable cache data. FanoutCache shards get one too, since each shard is itself a Cache instance. The write is best-effort (an OSError, e.g. a read-only directory, is swallowed) and idempotent ('xb' exclusive-create, skipped if the tag already exists). Cache.check()'s "unknown file" scan also needed to ignore the tag file, the same way it already ignores the sqlite DBNAME files, otherwise check(fix=True) would delete it as an untracked file. Co-Authored-By: Claude Sonnet 5 --- diskcache/core.py | 18 +++++++++++++++++- tests/test_core.py | 11 +++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/diskcache/core.py b/diskcache/core.py index 7a3d23b..7a431ac 100644 --- a/diskcache/core.py +++ b/diskcache/core.py @@ -39,6 +39,14 @@ def __repr__(self): ENOVAL = Constant('ENOVAL') UNKNOWN = Constant('UNKNOWN') +CACHEDIR_TAG = 'CACHEDIR.TAG' +CACHEDIR_TAG_CONTENTS = ( + b'Signature: 8a477f597d28d172789f06886806bc55\n' + b'# This file is a cache directory tag automatically created by diskcache.\n' + b'# For information about cache directory tags, see:\n' + b'#\thttps://bford.info/cachedir/\n' +) + MODE_NONE = 0 MODE_RAW = 1 MODE_BINARY = 2 @@ -453,6 +461,14 @@ def __init__(self, directory=None, timeout=60, disk=Disk, **settings): ' and could not be created' % self._directory, ) from None + tag_path = op.join(directory, CACHEDIR_TAG) + if not op.exists(tag_path): + try: + with open(tag_path, 'xb') as writer: + writer.write(CACHEDIR_TAG_CONTENTS) + except OSError: + pass + sql = self._sql_retry # Setup Settings table. @@ -1967,7 +1983,7 @@ def check(self, fix=False, retry=False): error = set(paths) - filenames for full_path in error: - if DBNAME in full_path: + if DBNAME in full_path or CACHEDIR_TAG in full_path: continue message = 'unknown file: %s' % full_path diff --git a/tests/test_core.py b/tests/test_core.py index 788afef..845b41b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -565,6 +565,17 @@ def test_least_frequently_used(cache): assert len(cache.check()) == 0 +def test_cachedir_tag(cache): + tag_path = op.join(cache.directory, dc.core.CACHEDIR_TAG) + assert op.exists(tag_path) + with open(tag_path, 'rb') as reader: + assert reader.read().startswith(b'Signature: 8a477f597d28d172789f06886806bc55\n') + + # The tag file should not be reported (or removed) as an unknown file. + assert len(cache.check(fix=True)) == 0 + assert op.exists(tag_path) + + def test_check(cache): blob = b'a' * 2**20 keys = (0, 1, 1234, 56.78, 'hello', b'world', None)