1 The Problem
We want a script that looks at a messy folder (downloads, say) and sorts every file into a subfolder by its type — images together, documents together, and so on. It teaches working with the file system: listing, moving, and creating folders programmatically.
2 How to Think About It
Think about the sorting logic, before any code:
.jpg, .pdf…) and decide its category. → 3. Make a subfolder for that category if it does not exist. → 4. Move the file in. Safe, repeatable, automatic.
3 The Build — explained part by part
Here is the complete organizer. Each part is explained below.
from pathlib import Path
import shutil
# Map file extensions to category folder names.
CATEGORIES = {
".jpg": "Images", ".png": "Images", ".gif": "Images",
".pdf": "Documents", ".txt": "Documents", ".docx": "Documents",
".mp3": "Audio", ".wav": "Audio",
".zip": "Archives", ".tar": "Archives",
}
def organize(folder):
folder = Path(folder)
for item in folder.iterdir():
if item.is_file():
category = CATEGORIES.get(item.suffix.lower(), "Other")
target = folder / category
target.mkdir(exist_ok=True) # make the folder if needed
shutil.move(str(item), str(target / item.name))
if __name__ == "__main__":
organize("./downloads")
print("Done organizing.")
Path object knows how to list, join, and inspect paths.CATEGORIES — a lookup mapping each extension to a category folder. Data-driven, so adding a type is a one-line change.
folder.iterdir() — list everything in the folder. item.is_file() skips sub-folders so we only move files.
item.suffix.lower() — the file’s extension (like
.jpg), lowercased so .JPG and .jpg match. CATEGORIES.get(..., "Other") looks it up, defaulting unknown types to “Other”.target.mkdir(exist_ok=True) — create the category folder;
exist_ok=True means “do not error if it already exists”.shutil.move(...) — actually move the file into its category folder.
exist_ok=True — the second run crashes because the folder already exists.mkdir(exist_ok=True) so re-running is safe..JPG falls through to “Other”.4 Test & Prove Each Part
We test the categorising logic — which folder each extension maps to — without touching the real file system.
CATEGORIES = {
".jpg": "Images", ".png": "Images",
".pdf": "Documents", ".txt": "Documents",
".mp3": "Audio",
}
def categorise(extension):
return CATEGORIES.get(extension.lower(), "Other")
def test_image():
""".jpg goes to Images."""
assert categorise(".jpg") == "Images"
def test_document():
""".pdf goes to Documents."""
assert categorise(".pdf") == "Documents"
def test_unknown():
"""An unknown type goes to Other."""
assert categorise(".xyz") == "Other"
Run with pytest -v. We test the decision (which category) separately from the action (moving files), so tests are fast and safe — they never move real files. Separating decisions from side-effects is a key habit for file-system code.
5 The Interface
What it expects
downloads/ containing:
photo.jpg, report.pdf, song.mp3What it returns
downloads/Images/photo.jpg
downloads/Documents/report.pdf
downloads/Audio/song.mp36 Run It & Automate It
python3 organize.pyTest on a copy first! Point it at a practice folder before a real one — it moves files for real.
Jenkins runs the tests automatically — each line explained below.
Done organizing.
# downloads/ now contains Images/, Documents/, Audio/ subfolders with files sorted inFileExistsError.exist_ok=True to mkdir so existing folders are fine.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Dry run mode. Print what would move without moving it. (Teaches: safe previews.)
- Undo log. Record every move so you can reverse it. (Teaches: logging actions.)
- By date. Sort into folders by month instead of type. (Teaches: file timestamps.)