🎓 A1Learner
🔍

Python Project: File Organiser

A file organiser is a tool that sorts a messy folder into tidy subfolders. The organiser chooses each subfolder from the suffix of a file name.

Read a dry-run flag

A flag with action store_true becomes True when the flag is present. This test passes the flag in a list, so no real command line is needed.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(["--dry-run"])
print(args.dry_run)

Log with a plain format

The format shows the level and the message, and nothing else. The stream part sends log lines to the same place as print, so line order stays fixed.

import logging
import sys

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s", stream=sys.stdout)
logging.info("Found 3 files")
logging.warning("Skipping a folder")

Choose a folder from the suffix

The suffix without its dot names the folder. An empty suffix falls back to the word other.

from pathlib import Path

files = [Path("notes"), Path("b.csv"), Path("a.txt")]
for path in sorted(files):
    folder = path.suffix[1:] or "other"
    print(path.name, "->", folder)

Report or move

One small function holds the dry-run choice. It prints a promise in dry-run mode, and a done message in real mode.

def report_move(name, dry_run):
    if dry_run:
        print(f"Would move {name}")
    else:
        print(f"Moved {name}")

report_move("a.txt", dry_run=True)
report_move("b.txt", dry_run=False)

Example

import argparse
import logging
import sys
from pathlib import Path

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s", stream=sys.stdout)

def build_messy_folder(folder):
    folder.mkdir(exist_ok=True)
    (folder / "notes.txt").write_text("notes", encoding="utf-8")
    (folder / "todo.txt").write_text("todo", encoding="utf-8")
    (folder / "data.csv").write_text("a,b", encoding="utf-8")
    (folder / "photo.png").write_text("fake image", encoding="utf-8")

def organise(folder, dry_run):
    for path in sorted(folder.iterdir()):
        if not path.is_file():
            continue
        target_folder = folder / (path.suffix[1:] or "other")
        target = target_folder / path.name
        if dry_run:
            logging.info(f"Would move {path.name} to {target_folder.name}/")
        else:
            target_folder.mkdir(exist_ok=True)
            path.rename(target)
            logging.info(f"Moved {path.name} to {target_folder.name}/")

def show_tree(folder):
    for path in sorted(folder.rglob("*")):
        print(path)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args()

    messy = Path("messy")
    build_messy_folder(messy)

    print("Dry run:")
    organise(messy, dry_run=True)

    if args.dry_run:
        print("Dry run only. No files moved.")
        return

    print("Real run:")
    organise(messy, dry_run=False)

    print("Final tree:")
    show_tree(messy)

if __name__ == "__main__":
    main()

Key points

  • Dry run: a practice run that reports what would happen and changes nothing
  • Use argparse to read a --dry-run flag from the command line
  • Use logging to report each move with a level word, like INFO
  • Use the suffix of a file name to choose its subfolder
  • A file with no suffix goes to a folder named other
  • sorted builds the full file list first, so new folders do not join the list
  • The script makes its own messy folder, so every run starts the same
  • A file organiser tidies a messy folder by sorting its files into subfolders.

In this example, build_messy_folder makes a folder named messy and fills it with four files. The dry run comes first. organise reports each move with logging and changes nothing. With the --dry-run flag, the program stops there. Without the flag, the real run makes one subfolder per suffix and renames each file into it. show_tree then lists every path under messy in sorted order. Run the file with no flags to see this output. Run it with --dry-run to see only the report.

Note

The log format in this project has no clock time in it.

Plain lines keep the output the same on every run.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Python Playground

Exercise

What does the --dry-run flag do in this project?