A Python typesetter for the reader.
Project description
🪻 About
Prose formats Python source to be legible at a glance. It aligns equals signs and colons vertically across consecutive lines, places one entry per line in dictionaries and lists, alphabetizes methods and fields within their groups, applies a singleton rule for colon padding, and treats code like prose rather than minified text.
[!NOTE] Alpha (
0.1.0). The eight rules below are stable, with additional rules planned for later releases.
🗞️ Philosophy
Code is read far more often than it is written. A reader's eye moves down a page and across adjacent lines looking for parallels, patterns, and shape. When every = sits at a different column and every collection is compressed onto one line, that shape disappears. Prose restores it: aligned columns let the eye skim, one-per-line collections make each entry a unit, alphabetized groupings give every reader the same landmarks.
The trade-offs minimalist formatters were built to avoid (wider diffs, more vertical scrolling, occasional re-alignment churn) no longer dominate the equation. Agentic assistants do most of the typing, and every modern code host offers whitespace-ignoring diffs. What remains is the daily experience of reading code.
🪄 Install & Usage
uv tool install prose-formatter
prose format path/ # rewrite files in place
prose check path/ # exit non-zero on violations
prose format --diff path/ # show the diff without writing
prose check --stdin < file.py # read from stdin
🪶 Rules
Eight rules ship in 0.1.0:
| Rule | Coverage |
|---|---|
align-colons |
Collection literals, Pydantic / dataclass fields, function signatures, and docstring Args: sections |
align-equals |
Consecutive assignments at the same indentation |
align-imports |
The import keyword in from ... import ... groups and as in import ... as ... groups |
alphabetize |
Classes, methods (grouped dunders → properties → privates → publics), enum members, Pydantic fields (required then optional), function parameters, keyword arguments, and from imports |
match-case-align |
Single-expression case bodies |
one-per-line-collections |
dict, list, and set literals, even when they fit inline |
singleton-rule |
Skips colon padding when only one item exists in the aligned group |
strip-trailing-commas |
Multi-line collections and signatures |
Example
Before:
from sklearn.cluster import AgglomerativeClustering
from loguru import logger
from collections import Counter
config = {"threshold": 0.7, "metric": "euclidean", "linkage": "ward", "n_clusters": None}
class Posting(BaseModel, extra="forbid"):
title: str
company: str
location: str | None = None
date_posted: date | None
def render(self, separator: str, include_location: bool, include_date: bool) -> str: ...
def _slug(self):
return self.company.lower().replace(" ", "-")
def key(self):
return f"{self._slug()}-{self.date_posted}"
After:
from collections import Counter
from loguru import logger
from sklearn.cluster import AgglomerativeClustering
config = {
"linkage" : "ward",
"metric" : "euclidean",
"n_clusters" : None,
"threshold" : 0.7
}
class Posting(BaseModel, extra="forbid"):
company : str
date_posted : date | None
title : str
location: str | None = None
def _slug(self):
return self.company.lower().replace(" ", "-")
def key(self):
return f"{self._slug()}-{self.date_posted}"
def render(
self,
include_date : bool,
include_location : bool,
separator : str
) -> str:
...
⚖️ Configuration
[tool.prose] in your pyproject.toml:
[tool.prose]
line-length = 88
target-version = "py310"
[tool.prose.rules]
align-colons = true
align-equals = true
align-imports = true
alphabetize = true
match-case-align = true
one-per-line-collections = true
singleton-rule = true
strip-trailing-commas = true
Every rule is independently toggleable.
🗺️ Composition
Prose runs as the second pass in a two-stage pipeline. The first pass owns tokens (line wrapping, quote normalization, indentation, blank-line discipline) and the second pass owns layout (alignment, alphabetization, the singleton rule, one-entry-per-line collections, trailing-comma stripping). Ruff is the canonical first pass, in that ruff format matches the token-level scope and its lint config shares the pyproject.toml root with [tool.prose].
ruff format && prose format
Running Prose first is incorrect. Prose's alignment math depends on already-settled line breaks, and an upstream re-wrap will undo per-line layout decisions, forcing a third pass.
Ruff Configuration
| Code | Conflict | Reason |
|---|---|---|
COM812 |
Lint re-adds trailing commas | strip-trailing-commas removes them in multi-line collections and signatures |
E203 |
Lint flags whitespace before : |
align-colons produces it in dict literals, dataclass fields, function signatures, and docstring Args: blocks |
E221 |
Lint flags multiple spaces before = |
align-equals produces it across consecutive assignments at the same indentation |
E272 |
Lint flags multiple spaces before import / as |
align-imports produces it across from ... import ... and import ... as ... groups |
E501 |
Lint flags lines past line-length |
A long member in an alignment group pads shorter lines rightward, occasionally past the configured limit |
skip-magic-trailing-comma |
Formatter re-expands collections by trailing-comma presence | prose format controls collection layout independently of comma signaling |
Other Tools
Black formats, Flake8 lints, and isort sorts, so each pairs with Prose at a different layer:
| Tool | Pairing |
|---|---|
| Black | Run Black with --skip-magic-trailing-comma, then Prose second. Black collapses collections that one-per-line-collections re-expands and preserves trailing commas that strip-trailing-commas removes |
| Flake8 | Add extend-ignore = E203, E221, E272 to .flake8 or setup.cfg (and C812 if flake8-commas is installed). Flake8 inherits the same pycodestyle codes Ruff inherits |
| isort | Run isort first, Prose second, with no configuration adjustment. Prose alphabetizes within isort's groups and aligns the import keyword that isort leaves un-aligned |
🪡 Integrations
Wire Prose into anything that runs on save, on commit, or in CI.
CI
- run: uv tool install prose-formatter
- run: prose check .
Format on Save
Any editor that supports run-on-save (VSCode's runOnSave, Vim's autocmd BufWritePost, JetBrains File Watchers) can shell out to prose format <file>.
Pre-Commit
Add a local hook to your .pre-commit-config.yaml:
- repo: local
hooks:
- id: prose
name: prose
entry: prose format
language: system
types: [python]
Swap entry: prose format for entry: prose check for the check-only variant.
🗜️ Development
One-Time Setup
Prose uses mise to manage every toolchain and CLI through a single mise.toml. Install mise, wire it into your shell, then mise install provisions the rest.
Install Mise
curl https://mise.run | sh
The installer drops the binary into ~/.local/bin/mise.
Wire Mise into Zsh
Three init files cover the three load contexts (every shell, login shells, interactive shells) so mise-managed tools resolve correctly whether you are inside an interactive terminal, a login shell, or a non-interactive subprocess:
# ~/.zshenv (sourced for every shell, including non-interactive)
export PATH="$HOME/.local/bin:$PATH"
# ~/.zprofile (sourced for login shells, before .zshrc)
eval "$(mise activate zsh --shims)"
# ~/.zshrc (sourced for interactive shells)
eval "$(mise activate zsh)"
.zshenv puts mise itself on PATH so the later eval lines can find it. .zprofile's --shims activation makes mise-managed binaries resolvable in non-interactive contexts (scripts, editors, GUI launches). .zshrc's full activation gives interactive shells the per-directory tool resolution and task discovery.
Clone and Provision
git clone https://github.com/Jybbs/prose.git
cd prose
mise install
mise install provisions:
| Tool | Purpose |
|---|---|
cargo-insta |
Snapshot test review |
maturin |
Rust → Python wheel builder |
python (3.14) |
Python interpreter for wheel builds |
rust (stable) |
Rust toolchain via rustup |
uv |
Python package and venv manager |
Daily Workflow
Tasks are defined in mise.toml and discoverable via mise tasks:
| Command | What it does |
|---|---|
mise build |
Compile in debug mode |
mise check |
Verify Rust source matches rustfmt without rewriting |
mise ci |
Lint + test + wheel (full local sweep) |
mise format |
Format Rust source with rustfmt |
mise lint |
Run clippy with all warnings as errors |
mise review |
Interactively accept pending snapshot diffs |
mise test |
Run all tests including insta snapshots |
mise wheel |
Build the wheel and install into .venv |
Editor
VSCode
Install rust-analyzer and Even Better TOML. The rust-analyzer extension bundles its own language server, so it works without additional global Rust installs.
Suggested user settings (apply to any Rust project):
"rust-analyzer.check.command": "clippy",
"rust-analyzer.imports.granularity.group": "module"
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file prose_formatter-0.1.2.tar.gz.
File metadata
- Download URL: prose_formatter-0.1.2.tar.gz
- Upload date:
- Size: 287.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a5325eb5a61643886f1d950b51bf3e7881a578da12691c5e3a426f26499a2be
|
|
| MD5 |
44a00ddddb48fcf8bb76f10439a8a6ac
|
|
| BLAKE2b-256 |
fac953685801c389c997f2445833d36677198524049cc26a9887e3b19470b39c
|
Provenance
The following attestation bundles were made for prose_formatter-0.1.2.tar.gz:
Publisher:
release.yml on Jybbs/prose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prose_formatter-0.1.2.tar.gz -
Subject digest:
8a5325eb5a61643886f1d950b51bf3e7881a578da12691c5e3a426f26499a2be - Sigstore transparency entry: 1436103496
- Sigstore integration time:
-
Permalink:
Jybbs/prose@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Branch / Tag:
refs/tags/0.1.2 - Owner: https://github.com/Jybbs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file prose_formatter-0.1.2-py3-none-win_amd64.whl.
File metadata
- Download URL: prose_formatter-0.1.2-py3-none-win_amd64.whl
- Upload date:
- Size: 2.0 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6bcbeaca6be8d7ec20a21ac7611223ae6b08da4c7c6f5400f6038f498763e95
|
|
| MD5 |
c26ffb31377dd009349b700bd1f4c50c
|
|
| BLAKE2b-256 |
63018a0912e6a18cc752cf8ca330e2a251f2be4b95fc4ad8d8072992fff528fe
|
Provenance
The following attestation bundles were made for prose_formatter-0.1.2-py3-none-win_amd64.whl:
Publisher:
release.yml on Jybbs/prose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prose_formatter-0.1.2-py3-none-win_amd64.whl -
Subject digest:
f6bcbeaca6be8d7ec20a21ac7611223ae6b08da4c7c6f5400f6038f498763e95 - Sigstore transparency entry: 1436103506
- Sigstore integration time:
-
Permalink:
Jybbs/prose@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Branch / Tag:
refs/tags/0.1.2 - Owner: https://github.com/Jybbs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file prose_formatter-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: prose_formatter-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.1 MB
- Tags: Python 3, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff16785f0ca64def0120aea0cb8484f2bff73a89a44ee6d23cc0677ff4b900a5
|
|
| MD5 |
61ef1cb72d8109d78d4c64c4d75c324a
|
|
| BLAKE2b-256 |
847e637959c74bbdfa340aa2e76fe60caf7280995c6926ee4708ae7a97a1cec2
|
Provenance
The following attestation bundles were made for prose_formatter-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on Jybbs/prose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prose_formatter-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
ff16785f0ca64def0120aea0cb8484f2bff73a89a44ee6d23cc0677ff4b900a5 - Sigstore transparency entry: 1436103502
- Sigstore integration time:
-
Permalink:
Jybbs/prose@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Branch / Tag:
refs/tags/0.1.2 - Owner: https://github.com/Jybbs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file prose_formatter-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: prose_formatter-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 2.0 MB
- Tags: Python 3, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5035d748510eff47bd58181b7079c6f394944194391a7b06a6931e6e60e3a01c
|
|
| MD5 |
17a27b6b163f37c2bef63b68a601e626
|
|
| BLAKE2b-256 |
b7273c6f61b5851fd5efac7034b6868161a29fe2df8144006cfba00cf0937b7b
|
Provenance
The following attestation bundles were made for prose_formatter-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on Jybbs/prose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prose_formatter-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
5035d748510eff47bd58181b7079c6f394944194391a7b06a6931e6e60e3a01c - Sigstore transparency entry: 1436103509
- Sigstore integration time:
-
Permalink:
Jybbs/prose@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Branch / Tag:
refs/tags/0.1.2 - Owner: https://github.com/Jybbs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file prose_formatter-0.1.2-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: prose_formatter-0.1.2-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 1.9 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
147d068a294ecdbe7e839f64262583bb9f81d99fff6e327c1a01a8505b617621
|
|
| MD5 |
781e00c1c4129dcd37a6d635290cb4e8
|
|
| BLAKE2b-256 |
8642d822811bb4687d4d3b03aefbce5924883b3f0ef3892482506257986f0653
|
Provenance
The following attestation bundles were made for prose_formatter-0.1.2-py3-none-macosx_11_0_arm64.whl:
Publisher:
release.yml on Jybbs/prose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prose_formatter-0.1.2-py3-none-macosx_11_0_arm64.whl -
Subject digest:
147d068a294ecdbe7e839f64262583bb9f81d99fff6e327c1a01a8505b617621 - Sigstore transparency entry: 1436103507
- Sigstore integration time:
-
Permalink:
Jybbs/prose@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Branch / Tag:
refs/tags/0.1.2 - Owner: https://github.com/Jybbs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file prose_formatter-0.1.2-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: prose_formatter-0.1.2-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 2.0 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b9e632c1c719fc7ef1422710c049289e0f76820d98d277dae4476cddbdfc765
|
|
| MD5 |
8cc69767189c9c75df71208bcb02b611
|
|
| BLAKE2b-256 |
04f5eb017cf29c2918aec33359114c1efcc10da2ff9bb68ad2dcccd53e5a505c
|
Provenance
The following attestation bundles were made for prose_formatter-0.1.2-py3-none-macosx_10_12_x86_64.whl:
Publisher:
release.yml on Jybbs/prose
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
prose_formatter-0.1.2-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
8b9e632c1c719fc7ef1422710c049289e0f76820d98d277dae4476cddbdfc765 - Sigstore transparency entry: 1436103499
- Sigstore integration time:
-
Permalink:
Jybbs/prose@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Branch / Tag:
refs/tags/0.1.2 - Owner: https://github.com/Jybbs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17fd332c60df8a52f0d4c7f8c4799744e0a61ae5 -
Trigger Event:
push
-
Statement type: