Automating the Boring Stuff
Expert · 18 min read · ▶ live playground · ✦ checkpoint
Automation starts when you notice yourself doing the same careful-but-dull task twice. A small Python script can turn that manual work into repeatable steps: make a safe workspace, find the files, inspect what would change, then write clean outputs without touching the originals.
This is the local version of the web and data skills you just learned. Instead of sending HTTP requests or cleaning a pandas table, you are pointing Python at folders, text files, CSV files, and JSON files you control.
Start with a sandbox
Automation is code that performs a repeated workflow for you. The first rule is not "make it fast." The first rule is "make it safe enough to rerun."
Use pathlib.Path for file paths. A Path object represents a file or folder address, and the / operator joins path parts without caring whether your computer uses / or \ internally.
Every live example below creates its own sample files before reading them. That is the habit to copy: an automation script should know its starting point, not depend on yesterday's browser session or a mysterious file on your desktop.
from pathlib import Path
sandbox = Path("automation_sandbox")
inbox = sandbox / "inbox"
reports = sandbox / "reports"
for folder in (inbox, reports):
folder.mkdir(parents=True, exist_ok=True)
(inbox / "2026-07-01 Mina.txt").write_text("Mina, 8 tasks\n", encoding="utf-8")
(inbox / "2026-07-02 Ravi.txt").write_text("Ravi, 5 tasks\n", encoding="utf-8")
print("Sandbox:", sandbox.as_posix())
for path in sorted(sandbox.glob("*")):
print("Folder:", path.as_posix())Sandbox: automation_sandboxFolder: automation_sandbox/inboxFolder: automation_sandbox/reports
mkdir(parents=True, exist_ok=True) says "create the folder and any missing parent folders; do not fail if it already exists." That makes the setup idempotent, meaning you can run it more than once and get the same usable starting state.
Discover files before changing them
File automation usually begins with discovery. Path.glob() finds paths that match a pattern. *.txt means "any filename ending in .txt inside this folder." **/*.txt means "text files in this folder and its subfolders."
Think of glob() as walking through labeled drawers with a checklist. You do not grab every paper on the desk; you ask for the exact shape your script is allowed to handle.
from pathlib import Path
sandbox = Path("discovery_sandbox")
inbox = sandbox / "inbox"
inbox.mkdir(parents=True, exist_ok=True)
samples = {
"2026-07-01 Mina.txt": "Mina, 8 tasks\n",
"2026-07-02 Ravi.txt": "Ravi, 5 tasks\n",
"notes.md": "# not a report\n",
}
for name, text in samples.items():
(inbox / name).write_text(text, encoding="utf-8")
for path in sorted(inbox.glob("*.txt")):
text = path.read_text(encoding="utf-8").strip()
print(f"{path.name} -> {text}")2026-07-01 Mina.txt -> Mina, 8 tasks2026-07-02 Ravi.txt -> Ravi, 5 tasks
Notice the script ignores notes.md. That is not an accident; it is the point. A good automation script says what it is allowed to touch.
Derive output names instead of overwriting
Batch renaming is where automation gets useful and risky. The safer beginner pattern is to derive output filenames, meaning "compute the new name," then write copies into an output folder. Once the result looks right, you can decide whether real renaming belongs in a separate script.
A dry run prints the actions a script would take without doing the risky part. Dry runs are how you make an automation script reviewable before it becomes powerful.
from pathlib import Path
sandbox = Path("derive_sandbox")
source = sandbox / "incoming"
output = sandbox / "cleaned"
source.mkdir(parents=True, exist_ok=True)
output.mkdir(parents=True, exist_ok=True)
samples = {
"Mina Report.txt": " DONE: 3 invoices \n",
"Ravi Report.txt": " DONE: 5 invoices \n",
}
for name, text in samples.items():
(source / name).write_text(text, encoding="utf-8")
planned = []
for path in sorted(source.glob("*.txt")):
clean_name = path.stem.lower().replace(" ", "-") + path.suffix
target = output / clean_name
planned.append((path, target))
print("DRY RUN")
for old_path, new_path in planned:
print(f"would write: {old_path.name} -> {new_path.name}")
print()
print("WRITTEN COPIES")
for old_path, new_path in planned:
clean_text = old_path.read_text(encoding="utf-8").strip().lower()
new_path.write_text(clean_text + "\n", encoding="utf-8")
print(f"{new_path.name}: {clean_text}")DRY RUNwould write: Mina Report.txt -> mina-report.txtwould write: Ravi Report.txt -> ravi-report.txtWRITTEN COPIESmina-report.txt: done: 3 invoicesravi-report.txt: done: 5 invoices
The original files are still in incoming. The cleaned files are in cleaned. That separation makes mistakes boring: delete the sandbox folder later, or rerun the script to recreate the output.
Try it - clean CSV and JSON outputs
This playground creates a tiny work folder, writes raw CSV and JSON files, cleans them with the standard library, and writes the cleaned results into a separate out folder. Change an email address or label, then rerun; the program rebuilds its own sample files each time.
csv.DictReader gives each CSV row as a dictionary keyed by the header names. csv.DictWriter writes dictionaries back with a known header order. json.loads() turns JSON text into Python dictionaries and lists; json.dumps() turns cleaned Python data back into JSON text.
The important part is not the exact fields. It is the shape: raw folder in, cleaning rules in code, clean folder out.
Keep bigger automations tool-shaped
Excel workbooks, PDFs, and images are files too, but they are not plain text. In real projects you usually choose a format-specific library for the job: one for spreadsheet cells, one for PDF pages or extracted text, one for image resizing or conversion. The safe workflow stays the same:
- Copy or create sample inputs in a sandbox.
- Read only the files your script is meant to handle.
- Print a dry-run summary before changing anything.
- Write outputs to a new folder or a new filename.
- Keep originals untouched until you have inspected the result.
Scheduling is also tool-shaped here. A scheduler is a tool that runs something at a chosen time or interval. It does not make your script smarter; it only runs it later. That means the script must already be repeatable, logged, and safe when run twice.
Static scheduling shape only - not run in this browser lesson:
weekday morning -> python cleanup_reports.py --dry-run
after review -> python cleanup_reports.py --applyThe dangerous operations are the ones that remove, overwrite, send, or publish: deleting files, replacing originals, sending emails, calling live APIs, or driving a browser. Treat those as the final step after inspection, not as the first demo. A dry-run list is often the difference between "I saved an hour" and "I renamed the wrong folder."
Build scripts in layers
A useful automation script usually grows in this order:
- Create or point at a sandbox.
- Discover candidate files with
glob(). - Print what you found.
- Read and clean one file.
- Derive an output name.
- Write output separately.
- Add a dry-run switch before destructive work.
That last switch is where the next lesson takes over. Today the values are written inside the program; in lesson 24.2, you will wrap them with argparse, Python's standard-library module for command-line options, so a script can accept real options like --dry-run, --input, and --output.
Checkpoint
Answer all three to mark this lesson complete