PythiloLesson 12 of 15 · progress kept in this browser

Errors at layer boundaries

Each layer should raise errors in its own vocabulary. A web handler should not catch sqlite3.IntegrityError, and the domain should not know HTTP status codes. At each boundary, catch what the inner layer raised and raise what the outer layer understands.

import sqlite3

class EmailTaken(Exception):
    pass

db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE users"
           " (email TEXT PRIMARY KEY)")

def add_user(email):
    try:
        db.execute("INSERT INTO users"
                   " VALUES (?)", (email,))
    except sqlite3.IntegrityError as err:
        raise EmailTaken(email) from err

add_user("ada@x.io")
try:
    add_user("ada@x.io")
except EmailTaken as err:
    print("taken:", err, "|", err.__cause__)

from err keeps the original exception as __cause__, so the traceback still shows the database error underneath.

Your task

Write SqliteUsers(conn) whose add(email) raises EmailTaken chained from the IntegrityError and whose get(email) returns the email or raises UserNotFound. Write handle(action, *args) returning (200, result) on success, (409, message) for EmailTaken, (404, message) for UserNotFound, both messages naming the email, and (500, "internal error") for any other exception.