Codeskill

Learn to code, step by step

Paths JSON CSV config

Reading and writing structured data on disk. You touched files in the intro tutorials; here we focus on patterns that scale to small apps and CLI tools.

pathlib for all path work

Stop hand-joining strings with slashes. Path reads clearly and handles OS differences.

from pathlib import Path

root = Path(__file__).resolve().parent
data_dir = root / "data"
data_dir.mkdir(exist_ok=True)

output = data_dir / "report.csv"
output.write_text("id,namen1,Alicen", encoding="utf-8")

JSON for config and interchange

JSON is the default for config files and API payloads. Always specify encoding.

import json
from pathlib import Path

def load_json(path: Path) -> dict:
    with path.open(encoding="utf-8") as handle:
        return json.load(handle)

def save_json(path: Path, data: dict) -> None:
    text = json.dumps(data, indent=2, ensure_ascii=False)
    path.write_text(text + "n", encoding="utf-8")

CSV for tabular exports

Use the csv module instead of splitting strings manually – commas inside fields will catch you out.

import csv
from pathlib import Path

def write_contacts(path: Path, rows: list[dict[str, str]]) -> None:
    fieldnames = ["name", "email"]
    with path.open("w", encoding="utf-8", newline="") as handle:
        writer = csv.DictWriter(handle, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(rows)

Environment-specific config

Keep secrets out of files you commit. Load defaults from JSON and override with environment variables.

import os
from pathlib import Path

def load_settings() -> dict:
    base = load_json(Path("config/settings.json"))
    base["database_url"] = os.environ.get("DATABASE_URL", base["database_url"])
    return base

Validate after loading

Files lie. Check required keys before you use them.

REQUIRED = {"host", "port", "debug"}

def validate_settings(data: dict) -> dict:
    missing = REQUIRED - data.keys()
    if missing:
        raise ValueError(f"settings missing keys: {sorted(missing)}")
    return data

Atomic writes for important files

Write to a temporary file, then rename. If the process dies mid-write, you do not leave a half-empty config behind.

import tempfile
from pathlib import Path

def atomic_write(path: Path, content: str) -> None:
    with tempfile.NamedTemporaryFile("w", encoding="utf-8", delete=False, dir=path.parent) as tmp:
        tmp.write(content)
        temp_name = Path(tmp.name)
    temp_name.replace(path)

Next: HTTP clients and APIs – fetching remote data, posting JSON, and handling failures without taking down your whole script.

PreviousExceptions and logging system