Skip to main content

A Python typesetter for the reader.

Project description

Prose

A Python typesetter for the reader

Rust 1.82+ Python 3.10+ maturin


🪻 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

prose_formatter-0.1.2.tar.gz (287.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

prose_formatter-0.1.2-py3-none-win_amd64.whl (2.0 MB view details)

Uploaded Python 3Windows x86-64

prose_formatter-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

prose_formatter-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (2.0 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

prose_formatter-0.1.2-py3-none-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

prose_formatter-0.1.2-py3-none-macosx_10_12_x86_64.whl (2.0 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

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

Hashes for prose_formatter-0.1.2.tar.gz
Algorithm Hash digest
SHA256 8a5325eb5a61643886f1d950b51bf3e7881a578da12691c5e3a426f26499a2be
MD5 44a00ddddb48fcf8bb76f10439a8a6ac
BLAKE2b-256 fac953685801c389c997f2445833d36677198524049cc26a9887e3b19470b39c

See more details on using hashes here.

Provenance

The following attestation bundles were made for prose_formatter-0.1.2.tar.gz:

Publisher: release.yml on Jybbs/prose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for prose_formatter-0.1.2-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 f6bcbeaca6be8d7ec20a21ac7611223ae6b08da4c7c6f5400f6038f498763e95
MD5 c26ffb31377dd009349b700bd1f4c50c
BLAKE2b-256 63018a0912e6a18cc752cf8ca330e2a251f2be4b95fc4ad8d8072992fff528fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for prose_formatter-0.1.2-py3-none-win_amd64.whl:

Publisher: release.yml on Jybbs/prose

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for prose_formatter-0.1.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 ff16785f0ca64def0120aea0cb8484f2bff73a89a44ee6d23cc0677ff4b900a5
MD5 61ef1cb72d8109d78d4c64c4d75c324a
BLAKE2b-256 847e637959c74bbdfa340aa2e76fe60caf7280995c6926ee4708ae7a97a1cec2

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for prose_formatter-0.1.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 5035d748510eff47bd58181b7079c6f394944194391a7b06a6931e6e60e3a01c
MD5 17a27b6b163f37c2bef63b68a601e626
BLAKE2b-256 b7273c6f61b5851fd5efac7034b6868161a29fe2df8144006cfba00cf0937b7b

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for prose_formatter-0.1.2-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 147d068a294ecdbe7e839f64262583bb9f81d99fff6e327c1a01a8505b617621
MD5 781e00c1c4129dcd37a6d635290cb4e8
BLAKE2b-256 8642d822811bb4687d4d3b03aefbce5924883b3f0ef3892482506257986f0653

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for prose_formatter-0.1.2-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 8b9e632c1c719fc7ef1422710c049289e0f76820d98d277dae4476cddbdfc765
MD5 8cc69767189c9c75df71208bcb02b611
BLAKE2b-256 04f5eb017cf29c2918aec33359114c1efcc10da2ff9bb68ad2dcccd53e5a505c

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page