Purpose

A module reads best when it exposes a single public entry point and keeps the rest of its work in private helpers. psl itself is built this way: every checker module under python_structure_linter/domain/checks/ defines exactly one public check() function, with all supporting logic tucked behind _-prefixed names.

This rule enforces that shape on the application layer. Each use-case module must define exactly one public function — pinned with visibility: public plus min: 1 and max: 1 — while any number of _private helpers may sit alongside it, unconstrained.

Configuration

rules:
  - name: one-public-function-per-use-case
    type: symbol-count
    description: Each application module exposes exactly one public function
    within: src/contexts/{ctx}/application/**
    symbol: function
    visibility: public
    min: 1
    max: 1
    ignore_files: [__init__.py]

within selects the .py files under each context's application layer. visibility: public counts only top-level names that do not start with _, so private helpers never affect the tally. min: 1 and max: 1 together pin the public count to exactly one: max catches a module with two public functions, and min catches a module that exposes none. ignore_files skips __init__.py, which re-exports rather than defines.

Violation Example

place_order.py grows a second public entry point. The _release_stock helper does not count — only the two public functions do:

# src/contexts/orders/application/place_order.py

def place_order(cmd: PlaceOrder) -> OrderId:
    order = Order.create(cmd)
    return order.id


def cancel_order(order_id: OrderId) -> None:   # a second public entry point
    _release_stock(order_id)


def _release_stock(order_id: OrderId) -> None:
    ...

Passing Example

Keep one public function and move the rest behind _ prefixes. Private helpers may multiply freely — the public surface stays at one:

# src/contexts/orders/application/place_order.py

def place_order(cmd: PlaceOrder) -> OrderId:
    order = Order.create(cmd)
    _reserve_stock(order)
    _charge_payment(order)
    return order.id


def _reserve_stock(order: Order) -> None:
    ...


def _charge_payment(order: Order) -> None:
    ...

Output

The reported line is the surplus symbol — the second public function, cancel_order:

$ psl check
src/contexts/orders/application/place_order.py:8
    [one-public-function-per-use-case] Each application module exposes exactly one public function
    2 top-level function definitions found, at most 1 allowed

Found 1 violation(s).