Skip to content

align-imports

An import block carries two kinds of structure that the reader's eye wants to follow as columns. The module column says where a thing comes from, and the name column says what's pulled in. When the two columns float at varying widths, every line reads as a fresh sentence rather than a row in a table.

gathers consecutive from ... import ... statements (or consecutive import ... as ... statements) into a shared column for the import (or as) keyword, leaving the module column flush left and the name column flush right.

The rule reads each block as the run of consecutive imports at the same indentation. A blank line, a comment, or a non-import statement resets the run. Pair with

to sort entries within each block before alignment, with to separate import groups by category, and with to canonicalize bare-versus-from before the alignment pass.

Configuration

KeyTypeDefaultMeaning
enabledbooltrueToggles the rule on or off.
max-shiftpositive int | 0 | false16The width-spread budget a contiguous run may shift to reach the shared column. A positive N caps the spread, 0 forbids any shift so every row sits flush, and false lifts the cap so a contiguous run folds into one column. To hold one row out of an otherwise-aligned group, mark it with # prose: skip.

max-shift bounds how far the import keyword may shift to align. The rule walks each block of imports in source order and grows a column while its width spread stays within the cap, breaking a fresh column at the first import that would exceed it. A max-shift of false lifts the cap so a contiguous block folds into one column, and 0 forbids any shift. The per-rule facets reference covers the full semantics.

The Canonical Case

A run of from ... import ... statements lines up on the import keyword, so the module column flushes left and the name column flushes right.

from collections import OrderedDict
from typing      import Optional
from sys         import path
from os.path     import join
python

More Examples

The reading-order break reaches import M as A runs by the same width-spread rule as from-imports. A wide module breaks the run, and the as keyword aligns within the surviving sub-run.

A run of from ... import ... statements whose module prefixes spread past the operator-alignment default still aligns on the import keyword, because

seeds a wider max-shift than the operator rules.

A run of import M as A statements aligns the as keyword across them. The widest module name fixes the shared column, and shorter rows pad between the module and as to reach it.

A comment between two imports, whether an own-line block comment or a trailing comment on an import line, breaks adjacency because the aligner aborts on any comment token in the inter-statement gap. Each comment-bracketed sub-run aligns only with its own contiguous neighbors.

A bare import M carries no as keyword to align against, so it breaks the surrounding import-as run. A non-import statement between imports breaks it the same way, and each side aligns only with its own contiguous neighbors.

No Change

Input whose gaps already carry the target width of spaces is idempotent under the rule. The from-import and import-as groups already sit on their columns, so the rule emits zero edits and the output equals the input byte-for-byte.