PythiloLesson 3 of 15 · progress kept in this browser

Dependency inversion

A sign-up service that builds its own SMTP client depends on email details, and every test of it sends mail. Invert that: the service declares the small interface it needs and receives any object that fits.

from typing import Protocol

class Mailer(Protocol):
    def send(self, to: str, text: str): ...

class Signup:
    def __init__(self, mailer: Mailer):
        self.mailer = mailer

    def register(self, email):
        self.mailer.send(email, "Welcome")

class PrintMailer:
    def send(self, to, text):
        print(f"to {to}: {text}")

Signup(PrintMailer()).register("ada@x.io")

The protocol lives in the service's layer, written in the service's words. Adapters in the outer layer implement it simply by having the right methods, so the import arrow points from the adapter towards the service, against the direction of the calls. That reversal is the inversion.

In tests, pass a fake: a small working implementation that records what it was asked to do. Afterwards, assert on what it recorded.

Your task

Write a protocol Mailer with send(to, subject), a FakeMailer that appends (to, subject) tuples to its .sent list, and Signup(mailer) whose register(email) lowercases the email, raises ValueError if it is already registered, and otherwise stores it and sends it the subject "Welcome".