1 · Inject the client registry behind one Clients port
app/flows.py:39–61 · app/dash.py (28× flows._c) · app/clients/*.py · tests/test_booking.py
Before — one global, reached through
flowchart TB
subgraph reach["reached through, not injected"]
F["flows.py business logic"]
D["dash.py endpoints"]
end
C{{"_c(name)
stringly switch
global dict _clients"}}
F --> C
D -. "flows._c('notion') ×28" .-> C
C --> G["clients/ghl"]
C --> N["clients/notion"]
C --> M["clients/missive"]
C --> S["clients/stripe …"]
T["tests"] -. "monkeypatch
flows._c" .-> C
classDef leak stroke:#dc2626,stroke-width:2px,color:#dc2626;
class D,T leak
After — port injected at the seam
flowchart TB
F["flows.py"] --> P
D["dash.py"] --> P
P{{"Clients port
typed: .notion .ghl .missive"}}
subgraph adapters["two adapters justify the seam"]
R["RealClients
(prod)"]
K["FakeClients
(tests)"]
end
P --- R
P --- K
R --> CL["app/clients/*"]
classDef deep fill:#0f172a,color:#fff,stroke:#0f172a;
class P deep
Problem
Every adapter is reached through a private global _c("name") that constructs on first use; dash.py crosses into it 28× and tests can only run by monkeypatching flows._c.
Solution
Pass a typed Clients object into flows and dash. Prod wires RealClients; tests wire FakeClients. The adapters in app/clients/ stay as they are — only the seam moves.
Wins
- Two adapters justify the seam — prod + test
- Accept dependencies, don't construct them
- Locality: construction in one place, not 9 branches
- Leverage: one port, every call site & test
- Deletes the
monkeypatch flows._chack _NoGHLstops lying — feature-gate at wiring
_c becomes a call into the injected port.