Exception chaining with raise from

RoadmapsPython

Overview

Exception chaining links a new exception to the original exception that caused it, using the syntax `raise NewError() from original_error`. This explicitly sets the `__cause__` attribute on the new exception. try: int('not a number') except ValueError as e: raise RuntimeError('config parse failed') from e When this traceback prints, Python shows both errors: 'The above exception was the direct cause of the following exception' — the original `ValueError` stays visible for debugging, even though the caller only sees `RuntimeError`. If you want to intentionally hide the original error instead (e.g. hiding database internals from a user), use `raise NewError() from None` to suppress the chain entirely — no `__cause__` is set and no 'direct cause' line is printed.

When building professional applications, you don't want low-level errors (like `psycopg2.IntegrityError`) leaking into your high-level business logic. You catch the database error and raise a clean domain error like `DuplicateUserError`. However, if you don't chain them, you lose the original database traceback, making debugging a nightmare. `raise from` solves this by giving callers a clean domain error to catch, while preserving the full diagnostic history for the logs.

Where used: Service layers hiding repository/database implementation details, API clients translating HTTP errors into SDK-specific exceptions, Suppressing sensitive tracebacks using `from None`

Why learn this

Code walkthrough

def parse_config(data):
    try:
        return int(data['port'])
    except (KeyError, ValueError) as e:
        raise RuntimeError("Invalid config file") from e

try:
    parse_config({})
except RuntimeError as e:
    print(f"High-level error: {e}")
    print(f"Low-level root cause: {type(e.__cause__).__name__}")

Focus: The caller catches `RuntimeError` cleanly, but the `__cause__` attribute retains the original `KeyError` for logging.

Common mistakes

Glossary

traceback
A detailed report showing the sequence of function calls that led to an error in your code. Example: `Traceback (most recent call last):`
suppress
To intentionally prevent an error message or signal from being shown or passed further up the chain. Example: `raise MyError from None`

Recall questions

Understanding checks

What special attribute is populated when using `raise NewError() from original_error`?

ValueError

The `from e` syntax explicitly links the original exception as the direct cause, storing it in the `__cause__` attribute of the new exception.

How do you completely hide the original exception's traceback when raising a new exception, preventing it from being printed?

By using the syntax `raise NewError() from None`.

Using `from None` explicitly suppresses exception chaining, hiding the original error completely from the logs.

Practice tasks

Translate a Database Error

Modify `create_user` to catch the `KeyError` thrown by `db_insert`. When caught, raise a `ValueError('User exists')` explicitly chained from the caught `KeyError`.

Challenge

Hide internal errors

Write `fetch_data()` that catches a `ConnectionError` and raises a `ServiceUnavailableError` from None to suppress the internal network stack trace.

Continue learning

Previous: try/except with specific exception types

Previous: Raising exceptions with raise

Previous: Custom exception classes

Return to Python Roadmap