"Python Custom Exception Hierarchy: Design It Right"

"A python custom exception hierarchy turns 'something broke' into errors your users can catch precisely: one base exception per package, shallow levels, rich attributes, and chained causes. This guide designs a complete hierarchy for a payments library — then shows the decision table, the five…

Your payments library raises ValueError("card declined") in one place and ValueError("invalid amount") in another. Your users can't tell them apart without string-matching the message — and string-matching error messages is a trap that breaks the moment you fix a typo. Meanwhile, every except ValueError in *their* codebase now catches *your* failures by accident. A python custom exception hierarchy fixes this at the design level: errors your users can catch precisely, at whatever granularity they choose, without ever parsing a string. What you'll build A complete 3-level exception hierarchy for a payments package, from root to attributed leaf The one-base-exception-per-package rule, and why it matters for library users Exceptions that carry machine-readable attributes ( code , amount ), not just messages Proper exception chaining with raise ... from (and when bare raise is right) The iron rule: inherit from Exception , never BaseException A decision table: custom exception vs built-in vs error codes vs result types How ExceptionGroup / except* (3.11+) fits alongside your hierarchy The five mistakes that ruin exception taxonomies, each with its fix Designing your python custom exception hierarchy Start from the user's except clause, not from your internals. Ask: *what will callers want to catch?* A payments user wants "any payment failure" (retry or abort), "card problems specifically" (ask the user for a different card), and "this exact decline" (show the decline code). That's three catch granularities — so that's three levels: PaymentError # catch-all: something in payments failed ├── CardError # the card / customer side failed │ ├── CardDeclinedError # issuer said no (has .code, .amount) │ └── InsufficientFundsError # a specific, actionable…

Explore