Skip to main content

spacify

CI PyPI

A fast formatter for the vertical rhythm of Python and Rust code. ruff format and rustfmt decide how a statement is laid out, but leave most of the blank lines between statements up to you. spacify fills that gap: it puts air around logical blocks and tightens runs of related statements, and it only touches the blank lines between statements.

Before:

def load(paths):
    seen = set()
    for path in paths:
        if path in seen:
            continue
        seen.add(path)
    total = len(seen)

    count = 0
    log(total)
    return total

After spacify:

def load(paths):
    seen = set()

    for path in paths:
        if path in seen:
            continue

        seen.add(path)

    total = len(seen)
    count = 0
    log(total)

    return total

spacify never fights your formatter. Every rule stays inside the blank-line bounds ruff format and rustfmt keep, and CI runs spacify and the formatter over CPython's standard library, ripgrep and tokio in both orders to check they reach a fixed point. It also checks that spacify leaves every Python file's AST, and every Rust file's tokens, unchanged. Checking CPython's Lib, without its tests, takes under half a second on 8 cores.

Install

uv tool install spacify    # or: pipx install spacify, pip install spacify

Use with prek or pre-commit

Put the hook after your formatter's (ruff-format, or a cargo fmt hook), so spacify sees its line wrapping:

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.16.9
    hooks:
      - id: ruff-format
  - repo: https://github.com/avanserv/spacify-pre-commit
    rev: v0.1.0
    hooks:
      - id: spacify

The hook installs the prebuilt wheel from PyPI, so you don't need a Rust toolchain.

Command line

spacify                   # format every Python and Rust file under the current directory
spacify src/ tests/a.py   # format these paths
spacify --check           # write nothing; exit 1 if any file would change
spacify --diff            # write nothing; print a unified diff
spacify - < a.py          # read stdin, write stdout (--stdin-filename names it)

The exit code is 0 on success, 1 when --check or --diff found changes, and 2 on an error such as a syntax error, in which case the file is left untouched. Directory walks respect .gitignore and skip the directories ruff skips by default. A file you name explicitly is always formatted, unless you also pass --force-exclude, which is what the hook does.

Languages

spacify formats every language it supports by default: Python (.py, .pyi) and Rust (.rs). To format only some, list them in the configuration, for example languages = ["python"]. Files in other languages are skipped, even when you name them, and stdin takes its language from --stdin-filename (Python without one).

Known limits

In Python, spacify parses with tree-sitter-python, which rejects a few valid constructs: type parameter defaults (def f[T = int](), PEP 696), a parenthesised starred subscript (a[*(b)]), and an attribute access broken after its dot onto a line indented less than the statement. A file using one exits 2 and is left untouched. Files must be UTF-8, as for ruff, and line endings that are a bare \r are refused the same way. Python 2 print and exec statements are not rejected, since tree-sitter cannot tell some of them from valid Python 3 such as print >>f, x.

In Rust, tree-sitter-rust refuses some nightly-only syntax, and such a file exits 2 untouched. spacify assumes rustfmt's default ceiling of one blank line, so it does not support a blank_lines_upper_bound above 1.

Rules

Each rule can be turned on or off. They all apply to sibling statements in the same block.

Rule Effect
blank-before-block At least one blank line before a block statement when another statement comes before it.
blank-after-block At least one blank line after a block statement, before the next statement.
blank-before-exit At least one blank line before an exit, in blocks of exit-min-block-statements (default 3) or more statements.
collapse-groups No blank line between two single-line statements of the same kind.

The added blank lines take priority over collapse-groups. Blank lines that have a comment in the gap are kept, because a comment marks a paragraph you meant. When a rule does add a blank line, it goes above the comments and attributes that belong to the next statement. What each term means depends on the language:

Python Rust
Block statements if, for, while, try, with, match, and their async forms if, match, for, while, loop, and bare, unsafe, async and const blocks
Exits return, raise, break, continue, a bare yield return, break, continue, and a function body's tail expression
Kinds that collapse assignments, augmented assignments, calls, del, assert, global/nonlocal, module constants let and assignments, compound assignments, calls (macros, .await and ? included)
Never touched gaps next to a def or class, after an import, after a docstring gaps next to an item (fn, struct, use, const, ...)
Switched off by # fmt: off/# fmt: on, # spacify: off/# spacify: on, # fmt: skip // spacify: off/// spacify: on, #[rustfmt::skip]
Most blank lines in a row 2 at module level (1 in a .pyi stub), 1 in a nested block 1

In Python, an assignment outside every function and class whose target names all have an uppercase letter and no lowercase one (MAX = 3, T = TypeVar("T"), A, *REST = items) is a constant, a kind apart from other assignments. Imports are never collapsed, because ruff's isort rules separate import sections with blank lines. In Rust, the tail expression of a closure, a match arm or a let x = { ... } block is a value rather than a return, so it stays tight.

Configuration

spacify reads [tool.spacify] from pyproject.toml, or the top level of a spacify.toml or .spacify.toml, which is the place for a Rust project. It uses the first one it finds walking up from the current directory, or the file you pass with --config.

[tool.spacify]
extend-exclude = ["generated", "migrations/*.py"]  # `exclude = [...]` replaces the defaults

languages = ["python", "rust"]                     # the default: every supported language

[tool.spacify.rules]
blank-before-block = true
blank-after-block = true
blank-before-exit = true
exit-min-block-statements = 3
collapse-groups = true

[tool.spacify.rust.rules]                          # overrides the shared rules for Rust only
exit-min-block-statements = 4

An exclude pattern containing a / matches the path relative to the config file. Any other pattern matches a single file or directory name.

Contributing

See CONTRIBUTING.md.

Metadata

Release files for spacify 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for spacify 0.3.0
File Size Uploaded Provenance Actions
spacify-0.3.0.tar.gz 34.8 kB Without provenance Details for spacify-0.3.0.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for spacify 0.3.0
File Actions
spacify-0.3.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for spacify-0.3.0-py3-none-win_amd64.whl
spacify-0.3.0-py3-none-musllinux_1_2_x86_64.whl Python 3 none Linux musl 1.2+ x86-64 Details for spacify-0.3.0-py3-none-musllinux_1_2_x86_64.whl
spacify-0.3.0-py3-none-musllinux_1_2_aarch64.whl Python 3 none Linux musl 1.2+ ARM64 Details for spacify-0.3.0-py3-none-musllinux_1_2_aarch64.whl
spacify-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details for spacify-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
spacify-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details for spacify-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
spacify-0.3.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for spacify-0.3.0-py3-none-macosx_11_0_arm64.whl
spacify-0.3.0-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details for spacify-0.3.0-py3-none-macosx_10_12_x86_64.whl

Total release size: 9.6 MB

Release files / spacify-0.3.0.tar.gz

Download URL spacify-0.3.0.tar.gz
Size 34.8 kB
Tags Source
SHA-256 checksum
How to use checksums
026be9e472d575fa474405a5e62c82f8df95cba86d686222119b47cc168de7d0
BLAKE2b-256 checksum
How to use checksums
2e4822ff42e2d3a5522ccb5b9c5a7392f2772e5cd994bb12c3348ed6cd7fd38a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Release files / spacify-0.3.0-py3-none-win_amd64.whl

Download URL spacify-0.3.0-py3-none-win_amd64.whl
Size 1.3 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
33dc0c315bf5ea15d4cf3bf57424e1d09760d8156ec7a7b3dc3a7f1f1b25852b
BLAKE2b-256 checksum
How to use checksums
293058c60d2cb593317845f17d74cbb1017a9b5ec7100d97c61760e6ee02daec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Release files / spacify-0.3.0-py3-none-musllinux_1_2_x86_64.whl

Download URL spacify-0.3.0-py3-none-musllinux_1_2_x86_64.whl
Size 1.5 MB
Tags Linux musl 1.2+ x86-64 Python 3
SHA-256 checksum
How to use checksums
c1398af610ab9f9e5a3d782221b3cb683bded74f2b3d59c96631137c305548d2
BLAKE2b-256 checksum
How to use checksums
269e18eb2df486c799f519399de8c2db683205d22475b1e83a22ec51569b2513
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Release files / spacify-0.3.0-py3-none-musllinux_1_2_aarch64.whl

Download URL spacify-0.3.0-py3-none-musllinux_1_2_aarch64.whl
Size 1.4 MB
Tags Linux musl 1.2+ ARM64 Python 3
SHA-256 checksum
How to use checksums
3ea285df6891922ed5c64b4fdcdc58ab4121fcd004ddf142fbf0cf4c98690aa9
BLAKE2b-256 checksum
How to use checksums
beb90cf76c2cf68448780106ba76d47bc76efc7498085a276d89d864fe3501ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Release files / spacify-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL spacify-0.3.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.4 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
e8e4f085084e481918a4ece635a103cce0ebef2df05b88f224bf3fc58b8806ab
BLAKE2b-256 checksum
How to use checksums
4e80411302564acbc3ac5208f182d21fb5457a0d59d0bc8b060afda6d74c4f12
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Release files / spacify-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL spacify-0.3.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.4 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
cfb0df253aade8e7b0c37106bb41fb9b0e226bf72b9be483502572192b688663
BLAKE2b-256 checksum
How to use checksums
1cdcc06cb7fe4cf88ac1220b0dc53338c6f53860b69a75fafd9f99f7d065d290
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Release files / spacify-0.3.0-py3-none-macosx_11_0_arm64.whl

Download URL spacify-0.3.0-py3-none-macosx_11_0_arm64.whl
Size 1.3 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
8dec8c458ac540b706509b5f5cf05b280f5ffe8c09019435c5321d6d16db0464
BLAKE2b-256 checksum
How to use checksums
dd237e91ad21bcb1f4bb6ae64106bb33dc547641f99143aa1499d34773681979
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Release files / spacify-0.3.0-py3-none-macosx_10_12_x86_64.whl

Download URL spacify-0.3.0-py3-none-macosx_10_12_x86_64.whl
Size 1.4 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
d978184e90ed4b6960dc937ce896f8a78cf7749b560c953504eefc4c4eb4f0a4
BLAKE2b-256 checksum
How to use checksums
dc8005cd5ead36c72e5c377dafffc908ed4c0465a00731eb5187dc6e32e5896d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}

Security for spacify 0.3.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page