From 51814d6dc5047ffe10447f117cc2725683ca1bf5 Mon Sep 17 00:00:00 2001 From: August Vi Date: Sat, 10 Oct 2026 21:49:32 -0700 Subject: [PATCH 1/5] Bump the version to 5.7.0 and write the release section of the changelog --- CHANGELOG.md | 7 ++++++- README.rst | 4 ++-- SECURITY.md | 7 +++---- diskcache/__init__.py | 4 ++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b1bae..ff3188d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ diskcache-community forked from DiskCache at upstream commit ebfa37c (3 March 2024), two commits after the 5.6.3 release. Each entry names the pull request in this repository. Imported upstream pull requests are referenced by number, and their commits keep their original authors in the git history. -## Unreleased +## 5.7.0 (unreleased) + +First release under the `diskcache-community` name: upstream commit ebfa37c plus the changes below. The import name stays `diskcache`; the `cache.db` schema, the file layout, the serialization modes, the memoize key format and the persisted settings are unchanged, so a 5.6.3 process opens a directory written by 5.7.0 and the reverse. ### Security @@ -17,6 +19,7 @@ diskcache-community forked from DiskCache at upstream commit ebfa37c (3 March 20 - Write bytes values without the `io.BytesIO` copy (upstream pull request 333) and fix a README typo (upstream pull request 365) (#3). - Accept either path separator in stored filenames, so a cache written on Windows opens on other systems and `check()` stays clean across platforms (#6). - A parameter name in the `ignore` set of `memoize` matches the argument whether it is passed positionally or by keyword; keys of calls that do not pass an ignored parameter positionally are unchanged (#8). +- Close the SQLite connections of threads that have exited, from `close()` and when a `Cache` is garbage collected; keep connections inherited across `fork()` unused in the child, as SQLite requires, and let `fork()` wait for transactions in flight in other threads (upstream issues 295, 325 and 336; upstream pull request 299 was not taken because it serializes every transaction) (#13). ### Packaging @@ -25,9 +28,11 @@ diskcache-community forked from DiskCache at upstream commit ebfa37c (3 March 20 ### Docs - Add the fork notice and migration steps to the README, plus `NOTICE`, `SECURITY.md`, `CONTRIBUTING.md` and this changelog; point the documentation and repository links at this fork and remove the upstream analytics, logo and funding links from the docs (#4). +- Record the changes in this changelog and extend the README fork notice (#12). ### Tests and CI - Replace the upstream CI with one `ci.yml` workflow: lint checks, tests on Python 3.10 to 3.14 on Linux, macOS and Windows, Django 4.2 and 6.0 jobs, and an import check without Django (#1). - Add golden compatibility fixtures written by diskcache 5.6.3 for every container, eviction policy, tag index, statistics, `JSONDisk`, memoized calls and file-backed values, with tests that read them with the current code and a reverse-direction test that reads a fork-written cache with 5.6.3 (#5). - Run the test suite in CI against a SQLite built from source with `SQLITE_DQS=0` (#10). +- Rework the release workflow: tag-version check, build and twine check, TestPyPI dry run by dispatch, PyPI publish through trusted publishing behind the `pypi` environment, and a GitHub release with this changelog section (#14). diff --git a/README.rst b/README.rst index 27b2ce8..c67d624 100644 --- a/README.rst +++ b/README.rst @@ -62,8 +62,8 @@ and no fixed upstream version exists. This fork publishes the same library as compatible with 5.6.3 caches and adds an opt-in ``SafeDisk``, selected with ``Cache(directory, disk=SafeDisk)`` and likewise on ``FanoutCache``, ``Deque``, ``Index`` and the Django ``DISK`` option; 6.0.0 makes safe -reading the default. No release has been published yet; ``SECURITY.md`` in the -repository describes the current state. +reading the default. 5.7.0 is the first release of the fork; ``SECURITY.md`` +in the repository describes what it does and does not change. What stays the same: diff --git a/SECURITY.md b/SECURITY.md index 0d20e2d..d705d1c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported versions -Security fixes go into the latest 5.x release of diskcache-community only. There are no maintenance branches for older releases; if you are on an older release, upgrade to the latest one. No release has been published yet; until the first one, `master` is the supported line. +Security fixes go into the latest 5.x release of diskcache-community only. There are no maintenance branches for older releases; if you are on an older release, upgrade to the latest one. ## Reporting a vulnerability @@ -16,13 +16,12 @@ You will hear back within a week. A confirmed problem is fixed in the next relea Where this fork stands: -- The code on `master` today reads pickles from the cache directory exactly as 5.6.3 does. Nothing is fixed yet. -- 5.7.0 adds an opt-in `SafeDisk` that unpickles only an allowlist of standard-library types and rejects everything else, and lets every container (`Cache`, `FanoutCache`, `Deque`, `Index` and the Django backend) use it. The default stays unchanged, so 5.7.0 opens every 5.6.3 cache. +- 5.7.0, the first release of this fork, reads pickles with the default `Disk` exactly as 5.6.3 does, so it opens every 5.6.3 cache. It adds an opt-in `SafeDisk` that unpickles only an allowlist of standard-library types, rejects anything outside it at write time and confines value paths to the cache directory; `Cache`, `FanoutCache`, `Deque` and `Index` take it through `disk=SafeDisk` and the Django backend through the `DISK` option. New cache directories are created with mode `0700`. - 6.0.0 makes safe reading the default and turns the trusted-pickle behaviour into an explicit choice, with a migration guide. Until you run a version where safe reading is the default or you have opted into it, treat the cache directory as trusted input: keep it on local storage that only the user running the process can write to, and do not share it with other accounts. -A note on scanners: vulnerability scanners match on the distribution name, so `pip-audit`, Dependabot and similar tools report nothing for `diskcache-community` even though the code on `master` has the same behaviour as 5.6.3. A clean scan is not evidence that this issue is fixed. Use the list above instead, and the release notes of the version you run. +A note on scanners: vulnerability scanners match on the distribution name, so `pip-audit`, Dependabot and similar tools report nothing for `diskcache-community` even though the default `Disk` in 5.7.0 behaves as it did in 5.6.3. A clean scan is not evidence that this issue is fixed. Use the list above instead, and the release notes of the version you run. ## Dependencies diff --git a/diskcache/__init__.py b/diskcache/__init__.py index 3357f05..8b6d11c 100644 --- a/diskcache/__init__.py +++ b/diskcache/__init__.py @@ -106,8 +106,8 @@ def _warn_if_upstream_installed(site_dir=None): __title__ = 'diskcache-community' -__version__ = '5.6.3' -__build__ = 0x050603 +__version__ = '5.7.0' +__build__ = 0x050700 __author__ = 'Grant Jenks' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2016-2023 Grant Jenks' From b46e1f30aaaa862ae18d5e4a1769501905201b78 Mon Sep 17 00:00:00 2001 From: August Vi Date: Sat, 10 Oct 2026 22:39:56 -0700 Subject: [PATCH 2/5] Expire the key through SQL in the incr and decr tests instead of racing a 100 ms timer, which failed twice on slow Windows runners --- tests/test_core.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index a58eb19..2c8113d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -751,21 +751,29 @@ def test_incr(cache): assert cache.incr('key', 2) == 8 assert cache.get('key', expire_time=True, tag=True) == (8, None, None) assert cache.delete('key') - assert cache.set('key', 100, expire=0.100) + assert cache.set('key', 100, expire=10) assert cache.get('key') == 100 - time.sleep(0.120) + expire_now(cache, 'key') assert cache.incr('key') == 1 +def expire_now(cache, key): + """Move the expiry of `key` into the past without waiting for the clock.""" + cache._sql( + 'UPDATE Cache SET expire_time = ? WHERE key = ?', + (time.time() - 1, key), + ) + + def test_incr_insert_keyerror(cache): with pytest.raises(KeyError): cache.incr('key', default=None) def test_incr_update_keyerror(cache): - assert cache.set('key', 100, expire=0.100) + assert cache.set('key', 100, expire=10) assert cache.get('key') == 100 - time.sleep(0.120) + expire_now(cache, 'key') with pytest.raises(KeyError): cache.incr('key', default=None) @@ -775,9 +783,9 @@ def test_decr(cache): assert cache.decr('key', 2) == 2 assert cache.get('key', expire_time=True, tag=True) == (2, None, None) assert cache.delete('key') - assert cache.set('key', 100, expire=0.100) + assert cache.set('key', 100, expire=10) assert cache.get('key') == 100 - time.sleep(0.120) + expire_now(cache, 'key') assert cache.decr('key') == -1 From 2bc20355269229652fe08784b8db3204141dc2c0 Mon Sep 17 00:00:00 2001 From: August Vi Date: Sat, 10 Oct 2026 22:44:17 -0700 Subject: [PATCH 3/5] Give each pytest-xdist worker its own Django cache directory so one worker's tearDown cannot clear another worker's keys --- tests/settings.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/settings.py b/tests/settings.py index 04aee85..7dcb598 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -124,7 +124,11 @@ # Caching -CACHE_DIR = os.path.join(BASE_DIR, '.cache') +# Each pytest-xdist worker gets its own directory, otherwise one worker's +# tearDown clears keys another worker has just set. +CACHE_DIR = os.path.join( + BASE_DIR, '.cache', os.environ.get('PYTEST_XDIST_WORKER', 'main') +) CACHES = { 'default': { From 3a826e02a9a9b418a11d5bfad7de9fd192adedde Mon Sep 17 00:00:00 2001 From: August Vi Date: Sat, 10 Oct 2026 22:45:03 -0700 Subject: [PATCH 4/5] Add the read-only caches, store_time and iterkeys tag filter to the 5.7.0 changelog section --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff3188d..ecc4963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ First release under the `diskcache-community` name: upstream commit ebfa37c plus - Create every directory the cache makes with mode `0700`, including missing parents and the value subdirectories, and leave existing directories alone; includes upstream pull request 359 (#7). - Warn at import time when the upstream `diskcache` distribution is installed next to `diskcache-community`, since both own the same package directory (#9). +### Features + +- Read-only caches (upstream issue 113): opening an existing cache writes nothing unless a setting differs from the stored one, so a `cache.db` snapshot stays byte-identical across runs; a cache whose database or directory is not writable opens read-only, as does `Cache(directory, read_only=True)`, with reads, iteration, `check()` and memoize hits working and every write raising `ReadOnlyError`, a subclass of `sqlite3.OperationalError` (#17). +- `store_time=True` on `Cache.get`, `FanoutCache.get` and `DjangoCache.get` appends the store time to the returned tuple after any other requested metadata (upstream issue 330) (#16). +- `Cache.iterkeys(tag=...)` iterates the keys with a given tag, using the tag index when enabled; includes upstream pull request 372 (upstream issue 341) (#16). + ### Fixes - Support SQLite builds compiled with `SQLITE_DQS=0`, such as conda-forge's sqlite builds of early 2025: single-quoted literals in the `Settings` triggers and statistics counters (upstream pull request 311), and caches written by earlier versions get their triggers rewritten on first open when the stored text differs (#3). @@ -36,3 +42,4 @@ First release under the `diskcache-community` name: upstream commit ebfa37c plus - Add golden compatibility fixtures written by diskcache 5.6.3 for every container, eviction policy, tag index, statistics, `JSONDisk`, memoized calls and file-backed values, with tests that read them with the current code and a reverse-direction test that reads a fork-written cache with 5.6.3 (#5). - Run the test suite in CI against a SQLite built from source with `SQLITE_DQS=0` (#10). - Rework the release workflow: tag-version check, build and twine check, TestPyPI dry run by dispatch, PyPI publish through trusted publishing behind the `pypi` environment, and a GitHub release with this changelog section (#14). +- Make the `incr` and `decr` expiry tests and the Django tests deterministic under pytest-xdist, so slow runners no longer fail them (#15). From 9b936a62b4786491bfcef040ab0c55fc22dcdd6c Mon Sep 17 00:00:00 2001 From: August Vi Date: Sun, 11 Oct 2026 11:54:29 -0700 Subject: [PATCH 5/5] Set the 5.7.0 release date in the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc4963..f5ef237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ diskcache-community forked from DiskCache at upstream commit ebfa37c (3 March 2024), two commits after the 5.6.3 release. Each entry names the pull request in this repository. Imported upstream pull requests are referenced by number, and their commits keep their original authors in the git history. -## 5.7.0 (unreleased) +## 5.7.0 (2026-10-11) First release under the `diskcache-community` name: upstream commit ebfa37c plus the changes below. The import name stays `diskcache`; the `cache.db` schema, the file layout, the serialization modes, the memoize key format and the persisted settings are unchanged, so a 5.6.3 process opens a directory written by 5.7.0 and the reverse.