Skip to main content
← All projects
L2CLI · automation· 6-12h total

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.

Python 3.12clickPillowpytestpathlib

Milestones (5 · ~9h)

  1. 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"
  2. 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"
  3. 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"
  4. 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"
  5. 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.

  1. 0-5s: 'CLI that organizes 10k files in 4s, with dry-run, MIME, date, and EXIF GPS modes.'
  2. 5-25s: live run on a real ~/Downloads folder with --dry-run, then real.
  3. 25-45s: pytest --cov in front of recruiter; show 92%.
  4. 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

click

click docs are the canonical reference for ergonomic Python CLIs — chosen by Flask, Black, and pip-tools.

shutil

Python stdlib shutil.move semantics on cross-device moves are subtle — the official docs are the source of truth.

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 →