Handling errors and recording what your program did when things went wrong. Printing debug lines works until it does not – usually at 2 a.m. On a server.
Catch specific exceptions
Bare except: hides bugs. Catch the errors you can handle and let the rest propagate.
import json
from pathlib import Path
def load_config(path: Path) -> dict:
try:
return json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError as exc:
raise RuntimeError(f"missing config: {path}") from exc
except json.JSONDecodeError as exc:
raise RuntimeError(f"invalid JSON in {path}") from exc
Chaining with raise ... From exc preserves the original traceback in logs – invaluable when debugging.
Custom exceptions sparingly
Define project-specific exceptions when callers need to distinguish your errors from library errors.
class ApiError(Exception):
def __init__(self, message: str, status_code: int) -> None:
super().__init__(message)
self.status_code = status_code
Logging basics
Use the logging module instead of print() for anything you might want to keep in production.
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
log = logging.getLogger(__name__)
log.info("Starting import")
log.warning("Rate limit approaching")
log.exception("Import failed") # includes traceback
Log levels in practice
DEBUG– verbose detail while developingINFO– normal lifecycle eventsWARNING– something odd but recoverableERROR– a operation failedCRITICAL– the process may be unusable
Configure once at the entry point
Library code should create loggers with getLogger(__name__) and not call basicConfig. Your CLI or Flask app configures handlers once at startup.
# app.py
import logging
from mytool import importer
def main() -> None:
logging.basicConfig(level=logging.INFO)
importer.run()
Fail fast on bad configuration
Validate settings at startup. A misconfigured app that limps along logging warnings is harder to fix than one that exits immediately with a clear message.
def require_env(name: str) -> str:
import os
value = os.environ.get(name)
if not value:
raise RuntimeError(f"environment variable {name!r} is required")
return value
Next: paths, JSON, CSV, and config files – the boring data plumbing most projects need sooner than you expect.

