I spent five years in banking operations before writing software for a living, and the reason this tool exists is muscle memory from that time: someone asks for the files to be tidied, deduplicated, backed up or triaged, and the person who volunteers learns very quickly that most file scripts are only one careless flag away from deleting the wrong thing.
So I built python-automation-toolkit as four tools behind one CLI, standard library only, with the safety behaviour written as features and then pinned in place by tests. Twenty of them, all running against pytest tmp_path sandboxes, so the test suite itself never touches a real file.
The four tools
organize sorts a folder into subfolders by extension or modification month. duplicates finds byte-identical files and optionally removes them. backup creates timestamped zip archives with rotation and restores them. logs pulls ERROR and CRITICAL lines out of a log, counts entries per level, or greps a regex.
None of that is clever. What is worth writing down is where each one was deliberately made hard to misuse.
Collisions create copies, not casualties
When organize moves a file into a destination that already contains that name, it does not overwrite. The destination path gets a suffix (report.pdf becomes report_1.pdf) and the move proceeds. I could have made the tool error instead, but a bulk sort job that aborts on the first collision teaches people to run it with a flag they found on a forum. Copying is the boring behaviour that is safe by default, and the tool can be run into a non-empty destination without reading a manual first.
Duplicate detection is two-phase on purpose
Byte-identical detection hashes every file with SHA-256, but only after a size pre-filter. Files are grouped by size first and only candidates in the same size group get hashed. On any realistic folder, sizes differ far more often than contents do, so the expensive part runs on a small subset. The block size for hashing is 64KB rather than whole-file reads, which keeps memory flat on large media files.
On the hash choice: MD5 would have been fine for "same bytes" detection in practice, but the tool's next step after a match is deletion, and I did not want the question "what if two different files collide on the digest" to live in the back of my head. SHA-256 closes it.
The destructive path asks to be watched
duplicates --remove will not run a silent pass. You can preview with --dry-run, which prints exactly what the real run would delete, line for line, and you choose what survives: the alphabetically first copy or the newest modification time (--keep newest). Invalid options fail loudly with an error rather than falling back to some default that deletes more than you asked.
That pattern repeated itself: backup create writes a fresh timestamped zip and rotation keeps only the newest N (--rotate 7 for a nightly rhythm); anything beyond that gets deleted, which is itself destructive, so rotation happens on an explicit flag rather than being baked into every create.
Determinism is a feature you can test
Archive members are written in sorted order, so two backups of an unchanged tree are byte-comparable. That sounds like a detail until you want to write a test that says "the backup contains exactly these files in exactly this order", which is one of the twenty. Sorting also means a diff between two backup zips tells you what changed in the source, which is a genuinely useful property I did not plan for.
The tests
The 20 tests cover three families: happy paths, safety behaviours (collision handling, dry-run preview, last-copy survival in a duplicate set), and failure modes (missing archive, invalid options). Everything runs in tmp_path sandboxes. The failure-mode tests are the ones I would keep even if I had to cut the suite down, because they are what stops a future refactor from quietly turning an error into a silent destructive fallback.
The CLI is 331 lines of Python with no dependencies. pytest is the only dev requirement. If that sounds too small, consider that most people who have lost files to a script lost them to something smaller.
Full repo: https://github.com/Reaver1000/python-automation-toolkit
Top comments (0)