Codeskill

Learn to code, step by step

Exceptions and logging system

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 developing
  • INFO – normal lifecycle events
  • WARNING – something odd but recoverable
  • ERROR – a operation failed
  • CRITICAL – 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.

PreviousType hints that help