Separate planning from changing
The planning function should not modify files. It returns source and destination pairs that can be printed, tested, exported, or approved. This keeps risky effects at the edge of the program.
Use pathlib
Path objects make filenames, suffixes, and directories easier to reason about across operating systems. Sort inputs so numbering is deterministic.
Reject collisions
A destination that already exists should stop the batch. Never depend on overwrite behavior when a customer file could be involved.
Add a dry run
Default to preview mode. A command-line --apply flag can make the change explicit later, but the first version can simply print the plan and wait for a human to inspect it.
Working example
from pathlib import Path
def build_plan(folder: Path):
files = sorted(p for p in folder.iterdir() if p.is_file())
plan = []
for index, source in enumerate(files, start=1):
destination = source.with_name(f"invoice-{index:03d}{source.suffix.lower()}")
if destination.exists() and destination != source:
raise FileExistsError(f"Refusing to overwrite {destination}")
plan.append((source, destination))
return plan
folder = Path("incoming")
plan = build_plan(folder)
for source, destination in plan:
print(f"{source.name} -> {destination.name}")
answer = input("Type APPLY to continue: ")
if answer == "APPLY":
for source, destination in plan:
source.rename(destination)