fileutils.rotate_file(keep=1) deletes the file it just rotated; keep=N retains N-1 generations
rotate_file removes the oldest generation after the rename ladder has already moved every file up by one, so the generation it just wrote is the one it deletes. With keep=1 the file is destroyed and no backup exists; with any keep=N, N-1 generations survive.
Reproduction (fresh clone at 967864f, Python 3.13 and 3.14, everything in a tempdir):
import os, tempfile
from boltons.fileutils import rotate_file
with tempfile.TemporaryDirectory() as d:
p = os.path.join(d, "f.txt")
open(p, "w").write("CRITICAL DATA")
rotate_file(p, keep=1)
print(sorted(os.listdir(d))) # [] — the file is gone, nothing was kept
with tempfile.TemporaryDirectory() as d:
p = os.path.join(d, "f.txt")
for i in range(1, 6):
open(os.path.join(d, f"f.{i}.txt"), "w").write(f"gen {i}")
open(p, "w").write("current")
rotate_file(p, keep=5)
print(sorted(os.listdir(d))) # ['f.1.txt', 'f.2.txt', 'f.3.txt', 'f.4.txt'] — five requested, four keptMechanism — boltons/fileutils.py, rotate_file:
fns = [filename] + kept_names
for orig_name, kept_name in reversed(list(zip(fns, fns[1:]))): # lines 720-723: f.4 → f.5, …, f → f.1
if not os.path.exists(orig_name):
continue
os.rename(orig_name, kept_name)
if os.path.exists(kept_names[-1]): # lines 725-726: then f.5 — just written — is removed
os.remove(kept_names[-1])The docstring promises "dropping any files beyond keep"; the code drops the file at keep. The removal belongs before the ladder (delete the current .keep so the ladder has room), or the ladder should stop at keep-1.
Note that tests/test_fileutils.py::test_rotate_file_full_rotation (and its _no_ext twin) currently assert not (tmp_path / 'test_file.5.txt').exists() after keep=5, i.e. they pin the off-by-one, so a fix arrives with those two tests updated. The behaviour has been this way since the function was introduced in 3bfcfdd (2024-11-28); git describe at HEAD is 26.1.0-24-g…, so it is still fixable before 26.1.1.
Found by an automated QA pass over the repository (Verdict); the reproduction above was re-run by hand on a fresh clone before filing.
Source: mahmoud/boltons