Skip to content

fix: make uproot.recreate physically truncate an existing file - #1697

Draft
ariostas wants to merge 1 commit into
scikit-hep:mainfrom
ariostas:fix-recreate-truncate
Draft

fix: make uproot.recreate physically truncate an existing file#1697
ariostas wants to merge 1 commit into
scikit-hep:mainfrom
ariostas:fix-recreate-truncate

Conversation

@ariostas

@ariostas ariostas commented Aug 5, 2026

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

Addresses finding 3 of "PR 1" in #1688. This is a regression, with a clear origin.

Before the fsspec migration, uproot.recreate truncated the path itself:

if isinstance(file_path, str):
    with open(file_path, "w"):
        pass
    sink = uproot.sink.file.FileSink(file_path)

#1016 / #1034 moved that into FileSink.__init__, which only truncates when the file does not already exist:

https://github.com/scikit-hep/uproot5/blob/main/src/uproot/sink/file.py#L52-L58

so recreating over an existing file opens it "r+b" and leaves every byte past the new fEND in place. Recreating a 100 kB path as an empty ROOT file left the physical size at 100 kB with fEND at 1658.

Restores the truncation in recreate, which is also the path create delegates to.

  • uproot.update is unaffected and keeps its "r+b" behavior.
  • uproot.create still raises FileExistsError before reaching this point.
  • Recreating from a file-like object is unchanged — it was not truncated before the migration either. Happy to extend it there too if you'd prefer.

Tests

tests/test_1688_recreate_truncates.py: physical size after recreating over a large non-ROOT file and over a larger ROOT file, plus guards that recreate still creates missing files and parent directories, that create still refuses to overwrite without touching the file, and that update does not truncate. 2 of the 5 fail on main.

Full suite passes locally (1028 passed, 90 skipped).

Before the fsspec migration (scikit-hep#1016, scikit-hep#1034), uproot.recreate opened the path with
mode "w", truncating it. Afterwards the truncation moved into FileSink, which
only calls _truncate_file when the file does *not* already exist, so recreate
over a pre-existing file opened it "r+b" and left every byte past the new fEND
in place. Recreating a 100 kB path as an empty ROOT file left the physical size
at 100 kB with fEND at 1658.

Truncate the path in recreate, restoring the documented "RECREATE" semantics.
uproot.update is unaffected and keeps its "r+b" behavior, and uproot.create
still raises FileExistsError before reaching this point. Recreating from a
file-like object is unchanged, as it was before the migration too.

Assisted-by: claude-code:claude-opus-5[1m]
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.88%. Comparing base (e598b61) to head (d60e1b1).

Additional details and impacted files
Files with missing lines Coverage Δ
src/uproot/writing/writable.py 79.89% <100.00%> (+0.05%) ⬆️

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant