Purpose

An errors module reads best when its filename names exactly the exception it raises: order_not_found_error.py holds OrderNotFoundError and nothing else. This recipe caps every module under an errors/ directory at a single top-level class.

It pairs with One Class Per Module. That rule already bounds every domain module at one class, and errors/ sits inside domain/**, so error files are covered twice. The point is the narrower, explicitly named rule: the violation is reported as one-error-per-file rather than the generic domain cap, and the guarantee survives even if you later relax or scope-exclude the broad rule for some other module.

Configuration

rules:
  - name: one-error-per-file
    type: symbol-count
    description: Each errors module defines a single error class
    within: src/contexts/{ctx}/domain/**/errors/**
    symbol: class
    max: 1
    ignore_files: [__init__.py]

within matches every .py file under an errors directory in any context's domain — the ** on either side lets errors sit at any depth, whether directly under domain or nested beneath a subdomain. symbol: class counts top-level class definitions; max: 1 caps each module at one; ignore_files skips __init__.py, which re-exports errors and defines no class of its own.

Violation Example

The orders context bundles two error types into one module:

# src/contexts/orders/domain/errors/order_errors.py

class OrderNotFoundError(Exception):
    """Raised when an order id has no matching order."""


class OrderAlreadyPaidError(Exception):   # a second error class in the same module
    """Raised when paying an order that is already settled."""

Passing Example

Give each error its own module, one class apiece:

src/contexts/orders/domain/errors/
├── __init__.py
├── order_not_found_error.py      # class OrderNotFoundError
└── order_already_paid_error.py   # class OrderAlreadyPaidError

Output

The line points at the surplus symbol — the second class that pushes the count past the limit:

$ psl check
src/contexts/orders/domain/errors/order_errors.py:7
    [one-error-per-file] Each errors module defines a single error class
    2 top-level class definitions found, at most 1 allowed

Found 1 violation(s).