Repository navigation
cache_on_files: a disk cache that notices when input files change - #3
Merged
Merged
Conversation
joblib.Memory keys its cache on the arguments a function was called with. For a function that takes a file *path* that is not enough: the path stays the same while the file behind it changes, and the stale result is returned forever. - file_signature(file): the os.stat fingerprint of a file (size, mtime, ctime, inode) or of a list of files, without reading the contents. hash_file(use_cache=True) now builds its cache key from it as well. - cache_on_files(*params): decorator that computes that fingerprint for the declared parameters on every call and folds it into the joblib cache key, where the body ignores it. Without any parameter names, every argument that points at an existing file is picked up automatically; a path that does not exist yet is left alone and hashed as the plain string it is. - can be chained on top of @memory.cache, which is then unwrapped and rebuilt with the same Memory, since a fingerprint can only take part in a key that joblib computes itself. Three details that are easy to get wrong and are covered by tests: @wraps on the cached function makes joblib follow __wrapped__ to the original signature and reject the extra argument; joblib cannot map keyword-only parameters of a *args function; and f(x) and f(x=x) are two different cache entries unless the call is normalised first. Also carries the in-progress hash_file(use_cache=) and list_files changes that were sitting in the working tree, as they live in the same regions of the same files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
carries the rename through the tests, which still imported the old name and made the whole test file fail to collect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Before, filecache selected ~/.cache/meg_utils/filecache when the caller gave no memory. That is a decision of the application, not of the library, and it hides a forgotten parameter: the results go to a directory that nobody asked for. Now the caller must give memory=..., or put the decorator on top of a memory.cache. If there is neither, filecache raises a ValueError. The error occurs at decoration time, so the problem shows at import. Memory(None) stays permitted. It is a cache that does nothing, and it lets an application switch the cache off with one value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Restore EPS output to savefig()'s vectors/ folder, dropped when PDF metadata support was added. - Disable seaborn's default palette desaturation in tornadoplot() so low_colour/high_colour render as given. - Add pypdf/pytest as declared test dependencies. - Fix long_df_to_array subset-column tests to assert the (correct) ValueError on ambiguous, order-dependent collapsing instead of asserting the old silent-collapse behaviour. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pip install -e . / pip install pytest never picked up the pypdf dependency added for test_vector_metadata_embedded_in_pdf, so CI kept failing that test even after the local fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
push already fires on every branch (branches: ["**"]), so PRs from branches in this repo were running the same workflow twice. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
joblib.Memorykeys its cache on the arguments a function was called with. For a function that takes a file path that is not enough: the path stays the same while the file behind it changes, and the stale result is returned forever.What
file_signature(file)— theos.statfingerprint of a file (size, mtime, ctime, inode), or of a list of files, without reading the contents. The same fieldshash_file(use_cache=True)already used inline, which now builds its cache key from this instead.cache_on_files(*params, memory=None)— decorator that computes that fingerprint on every call and folds it into the joblib cache key, where the body ignores it:A parameter may hold a list of files (order is part of the key), and the cache invalidates if any one of them changes.
Without parameter names, every argument that points at an existing file is picked up automatically. A path that does not exist yet is left alone and hashed as the plain string it is, so the result computed while it was missing is not reused once it appears:
It can also be chained on top of a joblib cache, to take the
Memoryand its options from there:This is not a chain of two caches. The fingerprint has to take part in the key that joblib computes, and joblib only hashes the arguments of the function it wraps itself — so the
MemorizedFuncbelow is unwrapped and rebuilt with the sameMemoryrather than called through.ignore=on the inner cache is refused with an assertion instead of silently no longer ignoring anything..uncached(the original function) and.cached(theMemorizedFunc, e.g. for.clear()) are attached to the result.Three details that are easy to get wrong
All three were found by the tests, not by reading:
@wrapson the cached function makesinspectfollow__wrapped__to the original signature, and joblib then rejects the extra argument. The identity is copied by hand instead — which is also why the function's source is hashed into the key explicitly, since joblib's own source-change invalidation would otherwise track the (never-changing) wrapper.*argsfunction, so the fingerprint and source hash are leading positional parameters.f(x)andf(x=x)are two separate cache entries unless the call is normalised to keyword form first.Tests
43 new tests in
tests/test_misc.py(TestFileSignature,TestCacheOnFiles,TestCacheOnFilesAuto,TestCacheOnFilesChained): invalidation on change, lists of files, positional vs keyword calls, two decorated functions not colliding in one cache dir,.uncached/.cached.clear(), auto-detection of non-paths and unusable strings, and the chained form landing in the rightMemory.pytest tests/test_misc.py→ 92 passed, 2 failed. The two failures are inTestLongDfToArraySubsetColumnsand are pre-existing — they fail on a cleanmaincheckout too.Note
The branch also carries the in-progress
hash_file(use_cache=)andlist_fileschanges that were sitting in the working tree; they live in the same regions of the same files, so they could not be separated into their own commit.🤖 Generated with Claude Code