alphabetize-siblings
OrderingSorts sibling entries whose order carries no meaning, covering import names, dict keys, class-body members, keyword arguments, and docstring entries.
band-constants moves module-level constants into two bands and sorts each, a leading band directly below the imports and a trailing band below the definitions, so a module reads top to bottom as its imports, its leading constants, its definitions, then the constants derived from them.
| Band | Members |
|---|---|
| Leading | a constant whose value reads only imports, builtins, literals, or other leading constants |
| Trailing | a constant that names a function or class defined later in the module |
The rule moves a constant into its band, and each band sorts by (tier, subcategory, name). A constant that reads another band member climbs one evaluation tier, and each tier opens its own blank-separated sub-band, so derived values read apart from the primitives they build on. A tier with a single constant sits tight below the tier above and aligns with it through align-equals.
A band carries its own order, whereas group-imports moves an import into its section and leaves the order within it to alphabetize-siblings. The split follows what each order costs to get wrong, in that import siblings reorder freely whereas a constant's slot binds every reference to it, so the move is only safe under the evaluation analysis this rule already runs.
Only an evaluation-time reference binds the order, covering a right-hand side, a decorator, a default argument, a base class, and a non-deferred annotation, so a constant a function reads inside its body still joins the leading band. Several cases pin a constant where the author left it:
# prose: keep marker.noqa comment marks, either bare or naming E402, the code the wider ecosystem reports a late import under.\ line join continues.A constant also stays put wherever moving it would change which object a name resolves to while the module runs. That covers:
global write from a call the module makes each count as that earlier binding.Each case resolves one object before the move and a different one after, without raising, so the constant keeps its slot instead.
A statement reading a dunder the module later rebinds keeps the whole region in source order, because the loader binds every module dunder before the body runs, so placing the rebind above the read would give it the new value. Every other name is unbound until its own statement runs, so a move above a reader can only resolve a reference, never change one.
Only an inert value bands, meaning one that reads names and builds a result (a literal, a name, an attribute or subscript read, a display or operator expression, or a lambda), whereas an effectful value carries a call, a comprehension, or an await, and moving it would reorder that work. RANDOM_SEED = 42 moves into the leading band whereas wide_trainer = L.Trainer(**trainer_kwargs) stays where it is.
A constant the analysis pins for a reassigned or unresolved name, a resolution hazard, or an effectful value still spaces as a member of the band beside it, so its pair with a banded constant sits tight, a tier boundary between them opens one blank line, and a heading standing a blank line above the pinned constant keeps one blank line above it. Every other pinned member keeps the gap the source wrote.
An own-line comment above a member travels with it wherever the rule places it, and a comment on the line directly below a member documents that member instead and follows it rather than leading it. A banner (# --- Configuration ---), a suppression directive, a tool pragma (# noqa), and a comment at another indent each keep their slot and pin the member beneath, so a band never crosses a banner. A notebook has the same reach as a module, with each cell boundary bounding the move.
The move and its spacing settle in one run, so the file reaches its final layout on the first format.
# the retry envelope the loader reads heads the whole group of constants below load with a blank line under it, and # per-attempt ceiling, in milliseconds sits directly above ATTEMPT_MS. The constants move into the leading band above load, and the blank line between the two comment blocks stays, with a second blank line opening between the literals and the two constants derived from them, so the heading and the bound note stay distinct however many times the file is formatted.
As written
import os
def load(path):
return os.stat(path)
# the retry envelope the loader reads
# per-attempt ceiling, in milliseconds
ATTEMPT_MS = 250
RETRY_LIMIT = 3
TOTAL_BUDGET_MS = ATTEMPT_MS
MAX_ATTEMPTS = RETRY_LIMIT
Run 1Rewritten
import os
# the retry envelope the loader reads
# per-attempt ceiling, in milliseconds
ATTEMPT_MS = 250
RETRY_LIMIT = 3
MAX_ATTEMPTS = RETRY_LIMIT
TOTAL_BUDGET_MS = ATTEMPT_MS
def load(path):
return os.stat(path)
Run 2No change
This run reads the previous output and rewrites nothing, so the file has reached its fixed point and every later run leaves it exactly as it stands.
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | bool | true | Turns the rule on or off. |
group-subcategories | bool | true | Clusters each band by subcategory, the type aliases first, then the SCREAMING_CASE constants, then the remaining module state, before sorting by name within each. false sorts by tier and name alone. |
max-tiers | positive int | false | 2 | Caps how many evaluation tiers get their own blank-line-separated sub-band, merging every deeper tier into the last. 1 keeps the whole band together and false gives every tier its own sub-band. |
The imports.first-party list under [imports] (see the configuration reference) sets which imports the leading band sits below, since a first-party package's imports group with the local-package section.
The facets below tune the band without switching it off, whereas band-constants = false leaves every constant in place among its neighbors.
group-subcategories group-subcategories clusters each band by subcategory, putting the type aliases ahead of the SCREAMING_CASE constants and those ahead of the remaining module state. Setting it to false sorts on (tier, name) alone, so each tier reads as one alphabetical run.
max-tiers max-tiers caps how many evaluation tiers open their own blank-separated sub-band, merging every deeper tier into the last. It defaults to 2 so a band reads as its base plus one derived sub-band, where 1 keeps the whole band together and false gives every tier a sub-band of its own.
SESSION = build and TIMEOUT = 30 sit together at the bottom of the module below build. TIMEOUT moves up to sit one blank line below import logging, and SESSION stays below the definition it names, because a constant whose value reads only imports, builtins, and literals belongs to the leading band, whereas one naming a definition further down belongs to the trailing band.
The module then reads top to bottom as its imports, its plain constants, its definitions, and the constants derived from them.
import logging
TIMEOUT = 30
def build(spec):
return logging.getLogger(spec)
SESSION = build
The comment # the tunable solver knobs heads the run of MAX_INT, ALPHA_DECAY, and PENALTY and describes the whole group rather than MAX_INT alone. The comment stays at the top of the group while the constants sort beneath it, so ALPHA_DECAY moves into first place and the heading does not follow MAX_INT into the middle.
The comment # the convergence floor the solver stops at sits directly below DEFAULT_EPSILON = 0.1, with a blank line between it and MAX_INT. The comment folds onto the line of DEFAULT_EPSILON as a trailing note and moves with it to the top of the group, because the blank line below the comment ties it to the constant above rather than the one below, so it stays with what it documents instead of sitting above whichever constant sorts into its old place.
__all__ = ["fft", "fftn"] sits at the bottom of the module, below fft and the free-standing comment # the reference implementation both transforms share that heads the definition. __all__ moves into the leading band beside _Norm, and the comment stays with fft as its leading comment, because a comment between two members leaves the move intact.
CONFIG = build opens the module, the default argument factory=Widget of build names the class, and class Widget sits between the two. CONFIG moves into the trailing band beneath both definitions, because build must be bound when the constant binds, and Widget already sits above def build(factory=Widget) so the default resolves when the definition runs.
REGISTRY = OrderedDict reads the name the try block above it imports, and the module also defines class OrderedDict further down. REGISTRY stays where it is written and TABLE = dict moves up past helper to join it, because an import inside a try arm counts as a binding, so moving REGISTRY below the class would change which OrderedDict it reads, whereas dict is a builtin nothing shadows.
HANDLER = make opens the module above the def make() it names, so running the file as written would raise a NameError. The constant moves into the trailing band beneath the definition, because its right-hand side reads make, and that name must be bound when the line runs.
LIMIT = TimeoutError reads the builtin, because it sits above the module's own class TimeoutError further down. LIMIT stays where it is written and TABLE = dict moves up past helper to join it, because moving LIMIT below the definitions would bind it to the class instead of the builtin, whereas dict is a builtin nothing in the module shadows.
REGISTRY = OrderedDict reads the imported name, because it sits above the module's own class OrderedDict further down. REGISTRY stays where it is written and TABLE = dict moves up past helper to join it, because moving REGISTRY below the definitions would bind it to the class instead of the import, whereas dict is a builtin nothing shadows.
DEFAULT = Selector reads the Selector that the if __debug__ branch above it binds, and the module also defines class Selector further down. DEFAULT stays where it is written and TABLE = dict moves up past helper to join it, because a write inside an if arm counts as a binding, so moving DEFAULT below the class would change which Selector it reads, whereas dict is a builtin nothing in the module shadows.
FIRST = TRACE[0] reads an element of TRACE rather than the list itself, and the body of class Setup appends to TRACE when it runs. FIRST stays below the class and LABEL = "ready" moves up past Setup into the leading band beside TRACE, because moving FIRST above the class would read TRACE before the append ran, whereas LABEL reads nothing the module mutates.
DEFAULT = Selector reads the Selector that setup writes into module scope through its global declaration when the module calls it, and the module also defines class Selector further down. DEFAULT stays where it is written and TABLE = dict moves up past helper to join it, because a global write from a call the module makes counts as a binding, so moving DEFAULT below the class would change which Selector it reads, whereas dict is a builtin nothing shadows.
LEVEL = logging.root.level reads state that the body of class Configure sets when it runs, since the class calls logging.basicConfig at definition time. LEVEL stays where it is written and TABLE = dict moves up past helper to join it, because moving LEVEL above the class would read the level before the class body configured it, whereas dict is a builtin the module never configures.
def helper(x=list) evaluates its default against the builtin list when the definition runs, and list = [1, 2, 3] rebinds that name further down the module. The rebinding stays below helper while TABLE = 5 moves up past it, because hoisting list above the definition would bind the default to the new list instead of the builtin.
PI and RADIUS read nothing, DIAMETER reads RADIUS, and AREA reads PI and DIAMETER, a chain three tiers deep. PI and RADIUS sort by name at the top, and one blank line opens below them before DIAMETER and AREA, because the default max-tiers of 2 folds every derived tier into one sub-band, with DIAMETER still above the AREA that reads it.
# the ceiling the scheduler reads stands a blank line above LIMIT = compute(), which calls compute and stays where it is written, while ZETA and ALPHA sort above it. The two blank lines above the heading close to one, the gap a tier boundary opens, rather than the heading sitting tight under ZETA, so the heading remains the constant's own on every later run.
# the ceiling the scheduler reads sits directly above LIMIT = compute(), which calls compute and stays where it is written, while ZETA and ALPHA sort above it. The blank line between the sorted pair and the heading closes, because a comment touching its constant heads that constant alone rather than the band, and a constant that stays in place still spaces as a member of the band beside it.
import mmm and import aaa # noqa: E402 both sit below VALUE = 1, with import zzz at the top of the file. mmm moves up to join zzz at the top, whereas aaa keeps its line, because E402 is the code the wider ecosystem reports a late import under, so a noqa marker naming it records the position as deliberate.
# the upstream cache time-to-live stands a blank line above TTL_SECONDS = 60, both below render. The comment and the constant move together into the leading band above render, and the blank line between them closes, because an own-line comment above a member moves with it even across a blank line.
TIMEOUT = 30 sits below fetch, whose body returns it. The constant moves into the leading band at the top of the module, above the definition, because fetch reads TIMEOUT only when called, so a reference inside a function body puts no constraint on module order.
DERIVED = BASE + 1 reads BASE, which places it one tier below BASE and OTHER. DERIVED moves to the bottom with no blank line above it, because a tier with a single constant sits tight below the tier above rather than opening a sub-band, so the three assignments stay one run.
zebra, foo, and bar_baz are bound above a # fmt: off block that wraps the hand-laid rows of matrix. The three sort among themselves and matrix never joins the run, because the run ends at the # fmt: off line, and the rows between the markers pass through exactly as written.
DERIVED_A and DERIVED_B read FIRST and SECOND, which name base, and LIMIT = compute() sits below them all. FIRST and SECOND sort, the derived pair opens a sub-band under one blank line, and a second blank line opens above LIMIT, because LIMIT calls compute and stays where it is written, and a constant that stays in place still spaces as a band member, so the tier boundary above it opens the same blank line a banded member would.
LIMIT = compute() sits two blank lines above SECOND = base and FIRST = base, all three below def base(). FIRST and SECOND sort and the two blank lines between LIMIT and the pair close, because LIMIT calls compute and stays where it is written, and a constant that stays in place still spaces as a member of the band beside it.
REGISTRY = build_default names a definition further down the file, and # the lazily-built registry stands a blank line above it. The constant moves into the trailing band beneath build_default, and the comment moves with it to sit directly above, because an own-line comment above a member moves with it even across a blank line, and that blank line closes.
Handler and Interval are type aliases, DEFAULT and MAX are SCREAMING_CASE constants, and setting and threshold are lowercase module state, interleaved in one run. Each kind clusters with its own and sorts by name within the cluster, the aliases first, the constants next, and the module state last.
X and Y read A and B, and P and Q read X and Y, so the six constants sit at three tiers, and the case runs with max-tiers = false. A blank line opens at every tier boundary, because the setting removes the cap, so each stage of evaluation reads as a small group of its own.
Handler = Callable[[Seconds], None] reads the Seconds alias and TIMEOUT = MAX + 1 reads MAX, so both sit one tier below the names they read. A blank line opens between the two tiers, and within each the alias sorts ahead of the constant, Seconds above MAX in the first and Handler above TIMEOUT in the second.
opener = TarFile.open binds an existing attribute under a lowercase name, Config = {"debug": True} builds a fresh dict under a PascalCase name, and MAX = 100 is a plain constant. opener sorts first as an alias, MAX follows as a constant, and Config sorts last as module state, because the cluster a binding joins is decided by the form of its value rather than the spelling of its name.
RAW = compute() calls compute, so it stays where it is written, and SCALED = RAW reads RAW, so it stays beneath it, whereas ALPHA and ZETA sort into the band below them. The blank line between RAW and SCALED closes, because a constant that stays in place still spaces as a member of the band beside it, so all four constants sit as one tight group.
X = list reads the builtin, and the next line list = [1] rebinds the name, with ZETA = 1 and ALPHA = 2 below. Nothing moves, ZETA and ALPHA included, because a sorted band could place the rebinding above the read and hand X the new list instead of the builtin, so the whole region keeps its source order. A builtin and a module dunder are both bound before the module body runs, and the rule reads both the same way.
zebra = compute_default() calls compute_default on its right-hand side, above alpha = 1 and beta = 2. zebra stays on the line where it is written, and the two constants beneath it keep their order, because a call runs code when the line executes, and moving the line would run it at a different point in the module than the author wrote.
__xname__ = __name__ reads the name the loader bound before the module body ran, and the next line rebinds __name__ to "decimal", with ZETA = 1 and ALPHA = 2 below. Nothing moves, ZETA and ALPHA included, because a sorted band could place the rebinding above the read and hand __xname__ the new value, so the whole region keeps its source order. A constant reading a name no earlier statement binds still moves up freely, since moving it up can only resolve the reference sooner.
from .pgen2 import token is unread, and it is the line the # Local imports comment sits above, with from ..pgen2 import driver below it. band-constants sorts the import band and moves the comment onto whichever import ends up first, so when prune-inert-imports removes token, from ..pgen2 import driver moves up onto the vacated line with the heading still above it, and the comment is never left over a gap.
The comment # the convergence floor the solver stops at sits on its own line directly below DEFAULT_EPSILON = 0.1, describing it, with MAX_INT = 5 after a blank line. band-constants moves the comment onto the end of the DEFAULT_EPSILON line as it packs and sorts the band, and align-equals then pads all three constants onto one = column, whereas a comment left on its own line would have split the run in two and left DEFAULT_EPSILON out of the alignment.
zebra, foo, and bar_baz sit above a # fmt: off block that contains the hand-laid matrix. band-constants sorts the three and align-equals pads their = into one column, and the matrix between # fmt: off and # fmt: on stays byte for byte as written, because the directives mark a region no rule edits and the run stops at that boundary.
convert names Alias in its annotations while Alias = int is written below it, under from __future__ import annotations. band-constants moves the assignment into the leading band, and prune-inert-imports removes the directive, because it counts the hoisted binding as written ahead of the annotations that name it.
convert names Sequence in its annotations while from collections.abc import Sequence is written below it, the case the directive exists for. band-constants moves the import above the definition, and from __future__ import annotations is removed, because the relocated binding counts as written ahead of the annotations that name it and they resolve without the directive.
LIMIT = 3 sits between from pkg import alpha and from pkg import beta, which would ordinarily keep the two apart. band-constants moves the constant below the imports, and reflow-imports merges the two pkg statements onto one line in the same run, because it measures against the order the move leaves rather than the order as written, so no second pass is needed once the constant has moved.
from pkg import a is never read and is the line the # Local imports comment heads, with X = 1 between it and from pkg import b. band-constants moves X = 1 below the imports, which brings the two pkg statements together, and with them adjacent from pkg import b moves up onto the line a vacates as a is removed, leaving the comment heading a real import rather than a gap.
Sorts sibling entries whose order carries no meaning, covering import names, dict keys, class-body members, keyword arguments, and docstring entries.
Moves each import in a run into its section, __future__ first, then bare, then external from, then local-package.
Sets the blank-line count between module-level definitions, class members, import groups, and the __main__ guard to PEP 8's canonical values.
Pads the space before = so consecutive assignments, annotated parameter defaults, and an exploded call's keyword arguments share one column.
Reports a module-level constant whose name is not SCREAMING_CASE and suggests the renamed form.
Reports a module-level SCREAMING_CASE name the module assigns more than once.