# Python exceptions — fail loud, named, at the source — worked examples

One bad → good per rule this skill teaches, taken from the fixture that proves the detector, so every pair is code that really fires and really passes.

### python-message-string-raise

`raise Exception/RuntimeError("…")` — a failure that names nothing, described in prose at the raise site

```py
----------[ Bad ]----------

def basket(context):
    if context.session is None:
        raise RuntimeError("There is no active session in this context.")
    return context.session.basket

----------[ Good ]----------

# in sessions.py
class NoActiveSession(LookupError):
    @classmethod
    def reading(cls, what: str) -> "NoActiveSession":
        return cls(f"there is no active session to read the {what} from")

# in sessions.py
def current_basket(context):
    if context.session is None:
        raise NoActiveSession.reading("basket")
    return context.session.basket
```

### python-raise-without-cause

`raise Other(...)` inside an `except` block with no `from` — the failure being handled left as an implicit context, never named as the cause

```py
----------[ Bad ]----------

def price(self, sku: str, refresh) -> int:
    try:
        return self.prices[sku]
    except KeyError as missing:
        self.prices = refresh(self.supplier)
        if sku not in self.prices:
            raise UnknownSku.at(self.supplier, sku)
        return self.prices[sku]

----------[ Good ]----------

def price_of(self, sku: str, refresh) -> int:
    try:
        return self.prices[sku]
    except KeyError as missing:
        self.prices = refresh(self.supplier)
        if sku not in self.prices:
            raise UnknownSku.at(self.supplier, sku) from missing
        return self.prices[sku]
```

### python-swallowed-exception

A bare `except:` or `except Exception` whose body only passes, continues or returns nothing — every failure, expected or not, made to vanish

```py
----------[ Bad ]----------

def load_catalog(path: Path):
    try:
        return Catalog(json.loads(path.read_text()))
    except Exception:
        return {}

----------[ Good ]----------

# in catalog_file.py
@dataclass(frozen=True)
class Catalog:
    products: dict

# in catalog_file.py
class CatalogUnreadable(Exception):
    @classmethod
    def at(cls, path: Path) -> "CatalogUnreadable":
        return cls(f"the catalog at {path} is not valid JSON")

# in catalog_file.py
def read_catalog(path: Path) -> Catalog:
    try:
        return Catalog(json.loads(path.read_text()))
    except json.JSONDecodeError as error:
        raise CatalogUnreadable.at(path) from error
```
