Skip to content

Fix test‑data extraction cache by judging archive file sha256 (#1844) - #2884

Open
Helen-Mars wants to merge 15 commits into
dimensionalOS:mainfrom
Helen-Mars:fix/1844-reextract-newer-archive
Open

Fix test‑data extraction cache by judging archive file sha256 (#1844)#2884
Helen-Mars wants to merge 15 commits into
dimensionalOS:mainfrom
Helen-Mars:fix/1844-reextract-newer-archive

Conversation

@Helen-Mars

@Helen-Mars Helen-Mars commented Jul 13, 2026

Copy link
Copy Markdown

Problem

Test data archives managed through Git LFS were not re-extracted when the archive was updated after the initial extraction.

The existing logic returned the already extracted path whenever it existed, which could leave stale test data in place after a newer archive was downloaded.

Closes #1844

Solution

  • Compare the sha256 checksum of the LFS archive with the checksum recorded in the metadata file within the extracted directory for freshness check.

  • Remove and re-extract the existing directory or file when the archive is newer.

  • Return the existing extracted path when the archive has not changed.

  • Write the current archive's sha256 checksum into a metadata file inside the extracted root directory after extraction to avoid repeated re-extraction.

  • Add tests covering:

    • missing extracted data;
    • existing up-to-date extracted data;
    • an updated archive that requires re-extraction;
    • a missing or deleted archive fallback;
    • a nested target path inside the extracted archive.
    • a single file processing

How to Test

uv run pytest dimos/utils/test_data.py::test_get_data_without_extracted_path -v
uv run pytest dimos/utils/test_data.py::test_get_data_with_existing_extracted_path -v
uv run pytest dimos/utils/test_data.py::test_get_data_with_updated_archive -v
uv run pytest dimos/utils/test_data.py::test_get_data_with_missing_archive -v
uv run pytest dimos/utils/test_data.py::test_get_data_with_nested_path -v
uv run pytest dimos/utils/test_data.py::test_get_data_with_single_file_archive -v

Or run all related tests:

uv run pytest dimos/utils/test_data.py -k "test_get_data"

Contributor License Agreement

  • I have read and approved the CLA.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refreshes extracted Git LFS test data when its archive changes. The main changes are:

  • Add SHA-256 metadata for extracted archives.
  • Re-extract stale directory and single-file archives.
  • Keep cached data available when its archive is absent.
  • Add coverage for cache, nested-path, and single-file flows.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
dimos/utils/data.py Adds archive checksum metadata and refreshes stale extracted data.
dimos/utils/test_data.py Adds cache-refresh coverage for directory, nested, and file archives.

Reviews (14): Last reviewed commit: "Merge branch 'main' into fix/1844-reextr..." | Re-trigger Greptile

Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment on lines +352 to +356
extracted_path.rename(backup_path)

try:
# Move the new extraction into the final location.
new_extracted.rename(extracted_path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Readers can lose paths

extracted_path.rename(backup_path) removes the directory path that prior get_data() callers hold before the replacement is published. A concurrent reader that opens or checks a nested file in the interval before new_extracted.rename(extracted_path) completes can receive FileNotFoundError. Staging protects against failed decompression, but these two renames do not preserve a continuously available published path. Coordinate refreshes with readers or publish the completed extraction through an atomic indirection.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this concurrency problem.
This PR solves two P‑1‑level bugs: avoiding stat() when archive missing and preventing data‑loss during decompression failures.
I plan to implement file‑lock in a follow‑up PR to solve the concurrent‑access issue.

Comment thread dimos/utils/test_data.py

@TomCC7 TomCC7 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! I'm proposing an alternative path here to address the freshness check.

  1. on first extraction add a metadata file into extracted folder root recording the original artifacts md5 sum
  2. use this checksum for freshness comparison instead of flaky mtime.

@paul-nechifor what do you think?

Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.13483% with 14 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/utils/data.py 83.58% 8 Missing and 3 partials ⚠️
dimos/utils/test_data.py 97.29% 3 Missing ⚠️
@@            Coverage Diff             @@
##             main    #2884      +/-   ##
==========================================
- Coverage   72.35%   71.83%   -0.53%     
==========================================
  Files        1024     1038      +14     
  Lines       92038    97071    +5033     
  Branches     8420     9172     +752     
==========================================
+ Hits        66598    69731    +3133     
- Misses      23168    25003    +1835     
- Partials     2272     2337      +65     
Flag Coverage Δ
OS-ubuntu-24.04-arm 66.21% <92.13%> (+0.80%) ⬆️
OS-ubuntu-latest 68.78% <92.13%> (+1.03%) ⬆️
Py-3.10 68.77% <92.13%> (+1.03%) ⬆️
Py-3.11 68.77% <92.13%> (+1.03%) ⬆️
Py-3.12 68.76% <92.13%> (+1.02%) ⬆️
Py-3.13 68.76% <92.13%> (+1.01%) ⬆️
Py-3.14 68.77% <92.13%> (+1.03%) ⬆️
Py-3.14t 68.76% <92.13%> (+1.02%) ⬆️
SelfHosted-macOS ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/utils/test_data.py 97.08% <97.29%> (+1.54%) ⬆️
dimos/utils/data.py 76.85% <83.58%> (+8.90%) ⬆️

... and 91 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@paul-nechifor

Copy link
Copy Markdown
Contributor

Thanks for the contribution! I'm proposing an alternative path here to address the freshness check.

1. on first extraction add a metadata file into extracted folder root recording the original artifacts md5 sum

2. use this checksum for freshness comparison instead of flaky `mtime`.

@paul-nechifor what do you think?

Yeah, I tried to do this initially, (I think using modified time), but then I realised it's not so easy so I created a ticket to do it later.

I imagine maintaining our own metadata is more reliable. One of the issues with timestamps is that you don't necessarily want the newest one. For example if you switch back to an old branch, you want the old archive.

Using a hash is better, but then you have to maintain this. Is it possible to get the SHA1 hash from git so you don't have to compute the sum yourself (like md5)? But then how do you know if the dir is up to date or not? Do you hash the dir?

I haven't really thought everything through... but it looks like you're handling it, so I'll leave it to you. 🙃 Thanks!

@TomCC7

TomCC7 commented Jul 16, 2026

Copy link
Copy Markdown
Member

@Helen-Mars Hi let me know if you need more context! You can re-request review once you are done addressing the comments.

Comment thread dimos/utils/data.py Outdated
@Helen-Mars

Copy link
Copy Markdown
Author

@Helen-Mars Hi let me know if you need more context! You can re-request review once you are done addressing the comments.

I implemented md5 freshness check, would you please review again, I hope to receive your reply.

@TomCC7

TomCC7 commented Jul 17, 2026

Copy link
Copy Markdown
Member

Yes can you address greptile's comment first? It seems legit.

Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
@Helen-Mars

Copy link
Copy Markdown
Author

Yes can you address greptile's comment first? It seems legit.

I fixed all Greptile AI problems, could you please review again when you are free .😊

@TomCC7 TomCC7 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @Helen-Mars md5 handling looks fine to me, but the program logic can be improved, can you address?

Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
extracted_path,
)

# Extract the updated archive into a temporary staging directory so

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there's no need to be this defensive here. Artifacts are expected to be working, using old artifacts only hinders the issue. Also this logic is duplicated with the extraction logic below, discarding the defensive logic can make the program lot cleaner.

1. verify if local artifact is stale
2. if no just early return
3. if yes backup the artifact
4. extract the new artifact

@Helen-Mars Helen-Mars Jul 18, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you. The current logic is a little redundant and I prefer way you gived, which is more simple and easy to maintain. I will update it next version.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't think this is resolved as well....

Comment thread dimos/utils/data.py Outdated

metadata_path = _get_archive_metadata_path(extracted_path)

metadata = {"archive_md5": md5_str}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's two way to avoid magic key here IMO.

  1. define MD5_KEY="archive_md5" on top of program
  2. or define a pydantic model like LFSArchiveMetadata and leverage it's json serialization and we just serialize and dump to file during write, and deserialize back to python object during read. https://pydantic.dev/docs/validation/dev/concepts/models/

For extensibility (in case we need more metadata fields) I recommend option 2 here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, using a pydantic model is better for safety and extension. I will optimize it next commit.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't think this is addressed....

Comment thread dimos/utils/data.py Outdated
@TomCC7

TomCC7 commented Jul 17, 2026

Copy link
Copy Markdown
Member

also would be good if you can fix ci btw

@Helen-Mars

Copy link
Copy Markdown
Author

also would be good if you can fix ci btw

I will try to do it.😊

Helen-Mars and others added 2 commits July 18, 2026 12:41
I make sense, this exception is redundant. Here archive file exists.

Co-authored-by: cc <55869557+TomCC7@users.noreply.github.com>
…up tests

- Replace mtime with MD5 checksum for staleness detection
- Use LFSArchiveMetadata (Pydantic) for read/write validation
- Remove redundant FileNotFoundError/NotADirectoryError catches
- Remove staging and rollback logic for simpler refresh flow
- Add nested path existence check before return
- Update tests for all scenarios (missing, up-to-date, updated, nested, single-file)
@Helen-Mars

Helen-Mars commented Jul 18, 2026

Copy link
Copy Markdown
Author

also would be good if you can fix ci btw

I finished corresponding fix and update including extra CI bug following your advice, looking forward to your reply.😊

@Helen-Mars
Helen-Mars requested a review from TomCC7 July 18, 2026 09:10

@TomCC7 TomCC7 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Helen-Mars are you sure you've addressed the comments? Did you forgot to push or something?

@Helen-Mars

Copy link
Copy Markdown
Author

@Helen-Mars are you sure you've addressed the comments? Did you forgot to push or something?

Let me check again, are you available in discord? could I talk about something fixing problem in discord. I think it is more direct.

@TomCC7

TomCC7 commented Jul 19, 2026

Copy link
Copy Markdown
Member

@Helen-Mars are you sure you've addressed the comments? Did you forgot to push or something?

Let me check again, are you available in discord? could I talk about something fixing problem in discord. I think it is more direct.

Yes can just dm me.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
@Helen-Mars

Copy link
Copy Markdown
Author

@Helen-Mars are you sure you've addressed the comments? Did you forgot to push or something?

Let me check again, are you available in discord? could I talk about something fixing problem in discord. I think it is more direct.

Yes can just dm me.

I am sorry I didn't update local to remote, I updated it just now. Hope you review again and receive your reply.

@Helen-Mars
Helen-Mars requested a review from TomCC7 July 19, 2026 03:10
Comment thread dimos/utils/data.py
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py Outdated
Comment thread dimos/utils/data.py
Comment thread dimos/utils/test_data.py
Comment thread dimos/utils/test_data.py
@Helen-Mars
Helen-Mars requested a review from TomCC7 July 22, 2026 08:32
Comment thread dimos/utils/data.py
extracted_path,
backup_path,
)
extracted_path.rename(backup_path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Restore failed refreshes This rename removes the published extraction before _pull_lfs_archive(), _decompress_archive(), and metadata writing have succeeded. If the updated archive cannot be pulled, is corrupt, or extraction fails due to an I/O error, the prior usable data remains only at the UUID backup path and later get_data() calls cannot return the cache. Extract and validate a replacement in staging, then publish it only after success; retain or restore the existing path on failure.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the time we should assure that LFS file is valid and Can be compressed successfully. When some mistake occurs due to an IO error caused from coding, Throwing an exception is more valid for us to recheck and fix our code. We backup user's file just give them chance to get their files Rather than do too much Over defensive coding.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a fair point — for a developer tooling context working with LFS archives, fail-fast exceptions are more appropriate than silent recovery. If the archive is corrupt or extraction fails, surfacing that loudly helps catch bugs early, and the backup serves its purpose as a user data safety net rather than a transparent recovery mechanism. The original comment over-applied a resilience pattern that fits end-user services better than internal dev tooling.

Shall I remember this as a rule: "Prefer fail-fast exceptions over silent recovery in developer tooling utilities; backups are for user data safety, not transparent state restoration"?

@Helen-Mars Helen-Mars changed the title Fix test‑data extraction cache by judging archive mtime (#1844) Fix test‑data extraction cache by judging archive file sha256 (#1844) Jul 23, 2026
@TomCC7 TomCC7 added the first-time-contributor PR opened by an author who had not previously committed to this repository label Jul 24, 2026
@TomCC7

TomCC7 commented Jul 25, 2026

Copy link
Copy Markdown
Member

@Helen-Mars Hi thanks for the effort! I have no more comment rn. However we are discussing a different model internally and that might reduce the logic a lot. I'll update here later.

@Helen-Mars

Copy link
Copy Markdown
Author

@Helen-Mars Hi thanks for the effort! I have no more comment rn. However we are discussing a different model internally and that might reduce the logic a lot. I'll update here later.

ok

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

first-time-contributor PR opened by an author who had not previously committed to this repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

get_data should extract again if the archive is newer than the extracted dir

3 participants