Python

Verified against ChatGPT · 2026-08-13

Design a Python exception hierarchy that tells your retry logic which failures are worth retrying

Produces a custom exception hierarchy and error-handling strategy for a service module that distinguishes retryable failures from fatal ones, instead of one flat except-Exception block deciding everything the same way.

ChatGPT4 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

Design an error-handling strategy for one specific Python module, including a custom exception hierarchy. The point is to make retryable failures and fatal failures behave differently everywhere this module is called, not to catch everything the same way.

MODULE AND WHAT IT DOES
A module that uploads generated PDF reports to S3 and records the resulting object key in the database.

EXTERNAL DEPENDENCIES IT CALLS
boto3 for S3 uploads, a Postgres connection via SQLAlchemy for recording the object key.

HOW IT'S CURRENTLY CALLED
Called from a Celery task; right now a failure just logs a traceback and the task is marked failed with no retry.

FAILURE MODES ALREADY SEEN
S3 throttling (SlowDown errors) under burst load; occasional Postgres connection drops; occasionally being asked to upload a report for a job_id that was already deleted.

RULES
Design a small exception hierarchy rooted in one base exception for this module, with subclasses that separate failures along the dimension that actually matters to a caller: is this worth retrying automatically (a transient network timeout, a rate limit, a lock-contention error) or not (a malformed input, a permission denial, a resource that genuinely doesn't exist)? Every subclass needs a one-line docstring stating specifically when it's raised and what a caller should do about it — "raised when X happens, caller should retry with backoff" versus "raised when Y happens, caller should not retry, this needs a code or data fix." For each external dependency listed, map its actual failure modes (a specific exception type it raises, or an HTTP status code it returns) onto your new hierarchy explicitly — do not let a third-party exception type leak up through this module uncaught, since that forces every caller to know about and handle a library-specific exception instead of this module's own contract. Attach enough context to each raised exception (the input that caused it, an identifier, not just a message string) that a caller or a log line can act on it without re-deriving what happened from scratch.

WHAT NOT TO DO
Do not create an exception subclass for every conceivable failure if two failures genuinely warrant the same caller behavior — a hierarchy with fifteen leaf types that all just mean "don't retry" is not more useful than three, it's just more surface area to keep in sync. Do not catch a broad `Exception` anywhere in this module's own code without immediately re-raising as one of the new custom types with context added — a caught-and-silently-logged exception here means the caller never finds out the operation didn't actually succeed.

OUTPUT FORMAT
1. The exception hierarchy as Python classes, each with its docstring.
2. A table mapping each known external failure mode to the custom exception it should become.
3. One code example showing the module raising the right custom exception with context attached.
4. One code example showing a caller using the hierarchy to decide retry vs. give up.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

Rooting the hierarchy in the retryable-versus-fatal distinction rather than a taxonomy of what went wrong mechanically matters because that's the actual decision a caller has to make with the exception, and it's a decision the built-in exception hierarchy can't express — `boto3` raising a `ClientError` for a throttled request and a `ClientError` for a permanently missing bucket look identical at the type level, so any caller catching that broad type has no structural way to know which behavior to apply and typically defaults to either always retrying (which spins forever on a permission error) or never retrying (which fails immediately on a transient throttle that would have succeeded on attempt two). Requiring the docstring to state what the caller should do, not just when the exception is raised, converts the hierarchy from documentation into an actual contract — a caller reading `RetryableUploadError` versus `FatalUploadError` knows the correct response without having to go read this module's internal implementation to figure out whether the underlying cause was transient. Mapping the specific known failure modes (S3 SlowDown, dropped Postgres connections, a deleted job_id) onto the new hierarchy explicitly forces the translation boundary to actually happen at this module's edge rather than being deferred — the alternative, letting `boto3`'s or SQLAlchemy's own exception types propagate up, means every caller across the codebase has to independently know which third-party exception types mean what, and that knowledge silently rots the moment the library's exception types change in a minor version bump. Capping the hierarchy's size by grouping failures that warrant identical caller behavior, rather than one subclass per distinct cause, keeps the hierarchy itself something a caller can hold in their head — an exception hierarchy nobody can remember gets handled with a blanket except-Exception anyway, which defeats the entire design.

What you get back

class ReportUploadError(Exception): pass class RetryableUploadError(ReportUploadError): """Transient failure (throttling, dropped connection) - caller should retry with backoff.""" class FatalUploadError(ReportUploadError): """Non-retryable failure (missing job, permission denied) - caller should not retry, needs investigation.""" # S3 SlowDown -> RetryableUploadError(job_id=..., cause='s3_throttle'); Postgres OperationalError -> RetryableUploadError; upload for a deleted job_id -> FatalUploadError(job_id=..., cause='job_not_found')

Verified against

ChatGPT GPT-5.1 · 2026-08-13

Changelog

  • 2026-08-13 Initial publish, verified against ChatGPT GPT-5.1.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Python prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY