Parsimony
A minimalist line-breaker that adds the fewest breaks to fit the line.
Unlike black/blue/ruff, which explode the outermost bracket first and stack every closing bracket on its own line, Parsimony makes the smallest change that fits an over-long line, using three complementary strategies.
Explode a bracket — open the minimum number of brackets and coalesce adjacent ones:
response = authed_client().get(reverse('forwarding:detail', args=[c.id]))
becomes
response = authed_client().get(reverse(
'forwarding:detail',
args=[c.id],
))
The same applies to the parenthesised containers in def/async def and
class headers — a long parameter list or base-class list is exploded one
element per line:
def some_method(self, param1: str, param2: int, param3: bool = False, param4: float = 0.0):
...
becomes
def some_method(
self,
param1: str,
param2: int,
param3: bool = False,
param4: float = 0.0,
):
...
Break a method chain — split a long .-chain one segment per line.
This is done for lines that can't be shortened by exploding a bracket:
queryset = SomeModel.objects.filter(active=True).order_by('-last_modified_at')
becomes
queryset = (
SomeModel
.objects
.filter(active=True)
.order_by('-last_modified_at')
)
Break a condition — split the boolean operators of an over-long
if, elif or while header one per line:
if some_condition_value and another_condition_value and a_third_condition_value:
...
becomes
if (
some_condition_value
and another_condition_value
and a_third_condition_value
):
...
Each keeps related code grouped and gives long expressions a clean one-operation-per-line shape.
Usage
Format files or directories (recurses for *.py):
parsimony -i src/ # rewrite in place
parsimony --check src/ # print a diff, exit 1 if anything would change
parsimony src/file.py # print formatted output to stdout
Read from stdin and write to stdout:
parsimony < src/file.py
cat src/file.py | parsimony
Installation
pip install kaapstorm-parsimony
Algorithm
While a physical line exceeds LINE_LENGTH, explode one container that
intersects an over-long line, chosen by:
- multi-item containers only (>= 2 args/elements); single-item containers are never opened — see "Contract" below.
- outermost (shallowest) of those.
A "container" is a call, list, tuple, set, dict or subscript, plus the
parameter list of a def/async def and the base/keyword list of a
class. A function's parens are implicit and a class's are optional
(class Bar:), but both explode like any other bracket. A trailing comma
after *args / **kwargs is valid in a def, so the exploded form stays
correct.
Then re-measure and repeat. "Explode" = each element on its own line at
a +4 hanging indent, with a trailing comma, and the closing bracket
dedented to the opening line's indent. Because we only open the chosen
container and never its single-item parents, adjacent openers like
get(reverse( remain coalesced. Coalescing is not about depth, only
about not opening single-item wrappers.
Exploding a container shifts its children one level deeper, so any breaks they already carry are re-indented to match — the same adjustment chain breaking makes.
When no multi-item container intersects a remaining over-long line, and
that line is an if, elif or while header whose condition is a
boolean expression, wrap the condition in parentheses and put a break
before every and / or. Operands the author already parenthesised stay
on one line — a hand-written group is a grouping decision, not a joint.
When neither applies, fall back to breaking the outermost method chain on
it (>= 2 call segments, as shown above): wrap it in parentheses and put
the head plus each .attr on its own +4 line. Bracket explosion is
preferred — a chain is only broken when opening a bracket cannot fix the
line — which keeps breaks minimal. Any brackets already opened inside a
segment are re-indented to stay aligned under their now-deeper segment.
Contract
The tool only ever adds breaks to over-long lines; it never removes or
rewrites existing breaks. This makes it idempotent, safe to re-run, and
safe to combine with hand-formatting. Preserving existing line breaks
is intentional, so it cannot be combined with ruff format, which would
re-flow its output back into the staircase. Pair it with ruff-as-linter
(E501 off) instead.
Multi-item-only is also a correctness guardrail: opening a single-item
subscript would turn x[0] into x[0,] (== x[(0,)]), which changes
meaning. Restricting to multi-item containers avoids that. It also
avoids ugly single-element splits.
Limitations
- Lines long for non-bracket, non-chain, non-condition reasons — ternaries, arithmetic chains, pure attribute chains (no calls), long string literals — are left untouched and reported, not fixed.
- Boolean expressions outside an
if/elif/whilecondition — in anassert, areturn, an assignment — are not broken. Neither is a negated condition (if not (a and b):), nor a boolean expression nested inside a bracket in the header (if check(a and b and c):). - No "join" pass: it will not re-flow code that another tool has already split. It only acts on lines that are physically too long.
- Comments inside brackets and pre-existing trailing commas are not specially handled.
Metadata
Release files for kaapstorm-parsimony 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| kaapstorm_parsimony-0.1.4.tar.gz | 41.7 kB | With provenance | Details for kaapstorm_parsimony-0.1.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| kaapstorm_parsimony-0.1.4-py3-none-any.whl | Python 3 | none | any | Details for kaapstorm_parsimony-0.1.4-py3-none-any.whl |
Total release size: 62.4 kB
Release files / kaapstorm_parsimony-0.1.4.tar.gz
| Download URL | kaapstorm_parsimony-0.1.4.tar.gz |
|---|---|
| Size | 41.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a38a283ce47cbf81c8837583170ef66cef73acd97b022ddb4776acdda3dae6b6
|
|
BLAKE2b-256 checksum How to use checksums |
28943c555ef7e69563a341c0c9a62ebe6ea0d97c0bd025f6b802a9cc50002de9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logRelease files / kaapstorm_parsimony-0.1.4-py3-none-any.whl
| Download URL | kaapstorm_parsimony-0.1.4-py3-none-any.whl |
|---|---|
| Size | 20.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f48c7eac41c449e07d63b1cac53dee9afd624f2540e95306476194ed2d5e12ea
|
|
BLAKE2b-256 checksum How to use checksums |
b7d22372c07ccfdb5867cd1c62541543ae2a4e54d389fbcc7c75f2c899f2a762
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logSecurity for kaapstorm-parsimony 0.1.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for kaapstorm_parsimony-0.1.4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.