Errors, Files & Config

Real programs fail. The difference between a demo and a system is how you handle it — files, config, and clean failure paths.

▶ Watch this reel

What you'll learn

  1. try / except / finally
  2. Custom exceptions
  3. Context managers (with)
  4. pathlib — modern paths
  5. JSON files
  6. Env vars & .env

Remember this

try / except / finally

Custom exceptions

class AppError(Exception): ...
class ConfigError(AppError): ...
raise ConfigError("missing key") from original

Context managers

pathlib

p = Path("data") / "raw" / "x.json"
text = p.read_text(encoding="utf-8")
Path("out").mkdir(parents=True, exist_ok=True)
list(Path("data").glob("*.csv"))

JSON

Env vars & .env

Code: Errors, files & config — production-shaped

import json, os
from pathlib import Path
from dotenv import load_dotenv

load_dotenv()                       # reads .env into os.environ
API_KEY = os.environ["OPENAI_API_KEY"]   # fail fast if missing

# --- custom exceptions ----------------------------------------
class AppError(Exception): pass
class ConfigError(AppError): pass

# --- config from JSON with real errors -------------------------
CONFIG_PATH = Path("config") / "app.json"

def load_config() -> dict:
    try:
        return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
    except FileNotFoundError as e:
        raise ConfigError(f"missing config: {CONFIG_PATH}") from e
    except json.JSONDecodeError as e:
        raise ConfigError(f"invalid JSON at line {e.lineno}") from e

# --- with = guaranteed cleanup ---------------------------------
with open("data/users.csv", encoding="utf-8") as f:
    header = f.readline().strip()

# --- pathlib everywhere ----------------------------------------
out = Path("output") / "report.json"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps({"rows": 42}, indent=2), encoding="utf-8")

csvs = sorted(Path("data").glob("*.csv"))