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).