File organizer CLI — sort Downloads by type, date, EXIF
Everyone has a Downloads folder full of junk. This project teaches the unglamorous-but-paid pattern of writing a CLI tool that real humans will actually run on their machines: argparse vs click, dry-run flags, exit codes, idempotency, error recovery mid-batch.
Resume bullet (when finished)
“Built a configurable file-organizer CLI in Python that sorts ~10k mixed files by MIME type, modification date, and image EXIF location in under 4 seconds; covered with 92% pytest line coverage and a dry-run mode.”
Locked tech stack
No "choose your language" — analysis paralysis kills completion. Follow the stack to the letter on your first build.
Milestones (5 · ~9h)
- M1~1h
Click skeleton + --dry-run
`organize ~/Downloads --dry-run` lists what would happen without touching disk.
CHECK BEFORE MOVING ON:
- Why is --dry-run the first feature you build, not the last?
- What's the difference between argparse and click — when does click win?
$ git commit -m "feat(cli): click skeleton + --dry-run" - M2~2h
Sort by MIME type
Detect MIME via `mimetypes` / magic-numbers; move files into `images/`, `documents/`, `archives/`, etc.
CHECK BEFORE MOVING ON:
- Why is the file extension not enough?
- What happens when two files would collide in the destination?
$ git commit -m "feat: MIME-based sorting + collision resolution" - M3~1h
Date buckets (by year/month)
Optional `--by-date` flag puts files into `2026/05/`. Uses `st_mtime`.
CHECK BEFORE MOVING ON:
- Modification time vs creation time — which is correct here and why?
- Timezone: should the bucket use UTC or local time?
$ git commit -m "feat: --by-date bucketing" - M4~3h
EXIF GPS sorting for photos
Optional `--by-location` reads JPEG EXIF GPS via Pillow, reverse-geocodes via a lightweight offline lib, organizes into `images/<country>/<city>/`.
CHECK BEFORE MOVING ON:
- What's the right behavior when EXIF is missing?
- Why offline reverse-geocode rather than calling an API?
$ git commit -m "feat: EXIF GPS-based sorting" - M5~2h
Pytest + CI
92%+ coverage. Tests use a temp dir; no network. GitHub Actions runs pytest on push.
CHECK BEFORE MOVING ON:
- What's the right way to test 'this CLI moved a file' without flake?
- Why does a CLI tool need CI?
$ git commit -m "test: 92% coverage + CI"
60-second demo storyboard
What you say in the recruiter screen when they ask "tell me about your latest project." Practice it out loud.
- 0-5s: 'CLI that organizes 10k files in 4s, with dry-run, MIME, date, and EXIF GPS modes.'
- 5-25s: live run on a real ~/Downloads folder with --dry-run, then real.
- 25-45s: pytest --cov in front of recruiter; show 92%.
- 45-60s: repo link + 'why I built it with click instead of argparse'.
STAR talking points for behavioral round
STAR — SAFETY-FIRST DESIGN
Situation: file-moving tools are scary — one bug can lose data. Task: build trust before code runs. Action: --dry-run was the first feature; added an undo journal that records every move so reversing is one command. Result: I shipped a tool I'd run on my own machine without flinching.
STAR — PERF OPTIMIZATION
Situation: first pass took 38s on 10k files. Task: get under 5s. Action: profiled with cProfile, found mimetypes.guess_type was the hot path; cached per-extension. Result: 3.8s for 10k files, 10× speedup.
Production references — how grown-up systems do this
Self-review rubric (before you claim done)
Correctness
- --dry-run never modifies disk.
- Collisions resolved deterministically (suffix `-1`, `-2`).
- Reverse-geocoding works offline.
- Undo command actually undoes.
Code quality
- Click commands typed, no inline lambda parsing.
- All file ops via pathlib, no string concat.
- Pure functions tested without touching disk.
Testing
- 92%+ coverage on `organizer/` package.
- Tests run in <2s in CI.
- Integration test in a tmp_path fixture covers MIME + date + EXIF paths.
Docs
- README has an animated GIF of the dry-run output.
- One paragraph on undo design.
- Install instructions via `pipx install`.
✱ AI code review
Get a senior-style review before you call it done
Push your finished work to GitHub, open a PR, paste the PR URL below. Claude reviews the diff against this project's rubric and replies with strengths, must-fix items, and one teachable principle.
Tick the rubric items honestly, write the README, push to GitHub, get the AI review above. Once it's clean, email support@learnpython.academy with the repo link — we feature the best ones on /success-stories.
Need Python first? Start Foundations →