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 reelWhat you'll learn
- try / except / finally
- Custom exceptions
- Context managers (with)
- pathlib — modern paths
- JSON files
- Env vars & .env
Remember this
- Catch specific exceptions, re-raise with bare raise, build domain exception trees
- with guarantees cleanup; pathlib replaces string paths; JSON ↔ dict via loads/dumps
- Config and secrets come from env vars; .env is dev-only and gitignored
try / except / finally
- Catch specific exceptions; bare
except:is a smell. except (A, B) as e:·finally:always runs.- Log then
raise(bare) — don't swallow.
Custom exceptions
class AppError(Exception): ...
class ConfigError(AppError): ...
raise ConfigError("missing key") from original
- Domain trees let callers catch broadly or narrowly.
Context managers
with X() as x:≡ C#using— cleanup on every exit path.- Roll your own:
contextlib.contextmanagerdecorator.
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
json.loads(text)→ dict ·json.dumps(obj, indent=2)→ text.json.load(f)/json.dump(obj, f)for file objects.- Raw dicts don't validate → typed parsing (Pydantic, Reel PY-09).
Env vars & .env
os.environ["KEY"](strict) ·os.getenv("KEY", default)..env+ python-dotenv for dev; gitignore it.- Secrets in git history = rotate immediately.
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"))