Skip to content

fix: preserve_in_zip replaces a stale archive member (#1414) - #1427

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/preserve-in-zip-replace-member
Jul 28, 2026
Merged

fix: preserve_in_zip replaces a stale archive member (#1414)#1427
Jammy2211 merged 1 commit into
mainfrom
feature/preserve-in-zip-replace-member

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Closes #1414.

AbstractPaths.preserve_in_zip added a file to a completed search's .zip only when that member was absent, so a post-completion artifact rewritten on disk left the stale bytes in the archive. Because restore() deletes the output directory and re-extracts the zip, the stale copy came back on the next resume — the case PyAutoGalaxy#516 creates, where a changed dataset mask invalidates files/galaxy_images_snr.fits and the images are recomputed. Correctness was never at risk (the stale cache is rejected on load), but such a search missed its cache on every run instead of once, repaying the max-log-likelihood-fit rebuild each time.

An existing member is now compared against the file on disk and replaced when it differs:

  • Comparison uses the file_size and CRC already in the zip's central directory, with a chunk-streamed zlib.crc32 over the file — the archived bytes are never decompressed and a large .fits cache is never read fully into memory.
  • Replacement streams every other member into a temporary archive beside the original (same directory, so the final os.replace is atomic) and swaps it in only once complete; a failure part-way leaves the original archive untouched. The replaced member's compress_type is carried over.
  • Byte-identical content stays a no-op, so the common resume path — re-preserving an unchanged cache — never pays for a rewrite. The no-zip and member-absent paths are unchanged.

Rebuilding the whole archive from the output directory (what the search itself does at completion via zip_directory) is not usable here: with remove_files: true the completed search's directory is deleted after zipping, so a later post-completion write recreates only files/<cache> and a full re-zip would truncate the archive to that one file.

The issue's other-callers check: autolens/analysis/result.py writes its multiple-image-positions cache only when the loose file is absent, so it never depended on replacement — no change needed. The related config knobs force_pickle_overwrite / force_visualize_overwrite do not overlap: they act on the search whose fit() is re-invoked, which always brackets the run with paths.restore() (which deletes the zip) and post_fit_outputzip_remove() (which rebuilds it wholesale). preserve_in_zip exists for the opposite seam — a write into an upstream completed search that is not fitted this run, whose zip is never rebuilt.

API Changes

None — no signature changes. AbstractPaths.preserve_in_zip gains behaviour: it now replaces an archived member whose content differs from the file on disk, where it previously left the archive untouched. Callers that write a changed artifact into a completed search's files/ folder get the refreshed copy after restore().
See full details below.

Test Plan

  • pytest test_autofit/non_linear/paths/test_paths.py — 18 passed
  • pytest test_autofit — 1559 passed, 1 skipped
  • New test__preserve_in_zip__replaces_stale_member fails on the pre-fix source (regression pinned), passes after
  • PyAutoGalaxy suite unaffected — 1009 passed (docstring follow-up on the same branch)
Full API Changes (for automation & release notes)

Changed Behaviour

  • autofit.non_linear.paths.abstract.AbstractPaths.preserve_in_zip(file_path) — an existing archive member whose content differs from file_path is now replaced (archive rewritten atomically) instead of being silently skipped. Unchanged: no-op when the zip does not exist, plain append when the member is absent, and no-op when the member is byte-identical.

Added (private)

  • autofit.non_linear.paths.abstract._matches_archived(info, file_path) — size + CRC comparison against the central directory.
  • autofit.non_linear.paths.abstract._replace_zip_member(zip_path, arcname, file_path) — atomic member replacement via a temporary archive.

Migration

  • None — no caller changes required.

Generated by the PyAutoLabs agent workflow.

`preserve_in_zip` only wrote a member that was absent from the search's
zip, so a post-completion artifact that was rewritten on disk (an
adapt-image cache invalidated by a changed dataset mask, PyAutoGalaxy#516)
left the stale bytes in the archive. `restore()` deletes the output
directory and re-extracts the zip, so the stale copy came back and the
search missed its cache on every run rather than once.

An existing member is now compared against the file on disk via the size
and CRC in the zip's central directory — no decompression, and the file
is read once in chunks — and replaced when it differs. `zipfile` cannot
overwrite in place, so the replacement streams the other members into a
temporary archive beside the original and swaps it in with `os.replace`;
a failure part-way leaves the original intact. Byte-identical content
stays a no-op, so the common resume path never pays for a rewrite.

Rebuilding the archive from the output directory (as the search does at
completion) is not usable here: with `remove_files` the directory holds
only the file just written.
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label Jul 28, 2026
@Jammy2211
Jammy2211 merged commit e60dee6 into main Jul 28, 2026
5 checks passed
@Jammy2211
Jammy2211 deleted the feature/preserve-in-zip-replace-member branch July 28, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-release PR queued for the next release build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: preserve_in_zip never replaces an existing archive member

1 participant