Rule Composition
Each case on this page pairs a small Python source with the rule set it turns on and shows what those rules write when they run together in Pipeline Order. A rule page shows the cases its own rule takes part in, whereas this page runs each case through the whole listed set. The cards below render the previewable cases from crate/tests/fixtures/composition/, and the binary's integration tests run every case in that directory.
The Canonical Case
One module-level constant exercises a rule from each family:
- Its values use the legacy
Union[…]form, so modernize-annotations rewrites them to the|operator and removes thetypingimport they read through. - The literal overflows
code-line-lengthon one line, so reflow-collections writes it one entry per line. - The entries sit in the order the author wrote them rather than alphabetical, so alphabetize-siblings sorts them.
- The keys then sit one per line in a column, so align-colons computes the padding from the widths the rewrite leaves.
- The removed import leaves a gap where it stood, so space-statements closes it.
The rules run against the same block, re-parsing between each so every later rule measures the rewritten source.
PRIMARY overflows its line with values written in the older Optional and Union forms, and target-version = "3.10" allows the | syntax. modernize-annotations rewrites each value to | form first, so every rule downstream measures the shorter text, and the dict then explodes, its keys sort so "delta_long" moves ahead of "gamma", and align-colons pads the : column against the rewritten widths. With nothing left reading either name, from typing import Optional, Union is removed.
PRIMARY = {
"alpha" : str | None,
"beta" : int | str,
"delta_long" : bool | None,
"gamma" : int | None
}
SECONDARY = "fallback"
The Cases
_openersis assigned a dict with no space after any:, aboveCHECK_DELAY = 100. strip-stranded-padding writes a space after each:, which widens the_openersrow by three columns.align-equals leaves the two
=unaligned, because it measures the row at the width it has once that edit is written, and padding_openersonto a shared column withCHECK_DELAYwould take the row past the 40-column budget.The
"alpha"and"beta"arms ofdispatcheach return a long inline dict, and the wildcard arm returnsNone. Each dict explodes to one entry per line, its keys sort so"comment_text"leads, and the:of each entry pads into one column within its arm, while align-match-case joins the wildcard'sreturn Noneonto itscase _:line.evaluateopens with threerecord.getassignments and then guards on a multi-lineBoolOp. align-equals pads the assignment run's=into one column, and align-comparisons right-aligns theis,==, and<operators in a column of their own inside theif, the two columns independent of each other.padx +=andborder +=sit in one run insidemeasure, each assigned a nestedwidget.tk.getint(widget.cget(...))call, and theborderrow runs past the 64-column budget. Thewidget.tk.getint(...)call on theborderrow explodes, itswidget.cget('border')argument moving to a row of its own, because reflow-calls measures the value from the row's own indent rather than one column short, while thepadxrow fits and stays whole.from .pgen2 import tokenis unread, and it is the line the# Local importscomment sits above, withfrom ..pgen2 import driverbelow it. band-constants sorts the import band and moves the comment onto whichever import ends up first, so when prune-inert-imports removestoken,from ..pgen2 import drivermoves up onto the vacated line with the heading still above it, and the comment is never left over a gap.visitnamesNodein its annotations and sits aboveclass Node, underfrom __future__ import annotations. alphabetize-siblings moves the class abovevisit, and the output reads as if the directive were no longer needed.from __future__ import annotationsstays, because prune-inert-imports runs before the sort, readsNodeas a binding the sort is free to move either way, and keeps the directive rather than depending on where the sort puts the class.import sys,import os, andimport jsonarrive out of order, and each module is used once below through a single attribute,os.getcwd(),sys.argv, andjson.loads. alphabetize-siblings sorts the statements sojsonleads, and bare-imports reports all three, each finding landing on the import's row in the sorted output, sojsonis reported on row 1 andsyson row 3.The comment
# the convergence floor the solver stops atsits on its own line directly belowDEFAULT_EPSILON = 0.1, describing it, withMAX_INT = 5after a blank line. band-constants moves the comment onto the end of theDEFAULT_EPSILONline 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 leftDEFAULT_EPSILONout of the alignment.The
ifcondition is wrapped in a grouping pair, with the line break inside thefindcall's own parentheses rather than inside the pair. reflow-parentheses joins the interior onto one row and drops the redundant pair in a single pass, because the pair owns none of the breaks, leaving reflow-calls a call that already fits its row, so it changes nothing.A one-line
configurecall carries a four-entry dict in itssettingskeyword. reflow-collections explodes the dict, reflow-calls re-indents it to the keyword column, alphabetize-siblings sorts the keywords and the dict entries, and align-equals and align-colons pad the=and:columns. The dict entries sit one indent step past the keyword column, with the closing}back at the keyword column.deque([(start, 0)]), {start}assigns a call and a one-item set on one row that crosses the 50-column budget. reflow-collections never breaks a one-item set, so the row's tail counts at its full width rather than only up to its{.deque(explodes with its list argument packed on a row of its own, where it fits, rather than the inner tuple exploding under a column the explode would then have moved.draw(w, h, d, c)passes four positional arguments to adef draw(width, height, depth, color)defined above it. reflow-calls explodes the call to keyword form, naming each argument after its parameter sowbecomeswidth = w, and alphabetize-siblings sorts the keywords socolorleads while align-equals pads their=into one column.The list in the
cccckeyword ofgcrosses the 40-column budget, and its first member is the callfn(100, 1000). reflow-collections explodes the list, and reflow-calls leavesfn(100, 1000)on one line, because exploding it would measure against a tail the explode is about to remove, so the call is measured where its entry ends up and fits there.some_function( argument_one_here, argument_two_here )carries a space just inside each parenthesis, and the row crosses the 66-column budget by exactly those two spaces. strip-stranded-padding removes them, and reflow-calls measures the argument list at the width that removal leaves, so the call stays on one row rather than exploding and joining back once the padding is gone.The tuple on the left of the
=breaks before its closing parenthesis, which the source indents under the tuple's first element. reflow-collections moves that closer to the indent of the row its opener sits on, the statement's own indent. reflow-calls then explodes thetype(self).prepare_context(...)call after the closer with its arguments hanging from the statement's indent, because the call is measured from that row rather than from the continuation row the source wrote.One key of
_hash_actioncarries a stranded space before its closing parenthesis,(False, False, False, True ), and strip-stranded-padding removes it. align-colons pads the run onto one:column, because it measures each row at the width it has once that removal is written, whereas the padded width would have read as a breach of the 40-column budget and the run would have skipped its column over a breach that never happens.import sys, osandimport myapp.core, abceach name two modules in one statement, withmyappinfirst-party. reflow-imports splits each into one module per line ahead of group-imports and alphabetize-siblings, so every split-off module reaches its own group and its sorted position, andmyapp.coreends up in the local section rather than beside the stdlibabcit was joined to._handlerscarries a trailing comment written#map of ..., tight against the hash, above_handlerList, and normalize-comment-spacing later writes one space after the#._handlersand_handlerListstay unaligned, because align-equals counts that space before it sets its column and a shared column would carry the_handlersrow past the 79-column budget, whereas_okand_statusbelow pad onto one column as usual.from pkg import ais unread and sits directly under the# Local importscomment, with a blank line between it andfrom pkg import b. reflow-imports merges the two statements across that blank line, so when prune-inert-imports removesa,from pkg import bmoves up onto the lineahad, the blank line goes withb's old position, and the comment ends up heading the import that survives.from .pgen2 import tokenis the first line under the# Local importscomment, and nothing in the module reads it. reflow-imports merges that line into its.pgen2sibling, so once it goes the comment headsfrom .pgen2 import driver. prune-inert-imports moves the sibling onto the vacated line rather than leaving the move to the merge on a later pass, which keeps a real import under the comment at every step.zebra,foo, andbar_bazsit above a# fmt: offblock that contains the hand-laidmatrix. band-constants sorts the three and align-equals pads their=into one column, and thematrixbetween# fmt: offand# fmt: onstays byte for byte as written, because the directives mark a region no rule edits and the run stops at that boundary.function_nmtakes a single parameter whose default is a nestedouter_call_name(...)call, and the signature crosses the 66-column budget. reflow-signatures puts the parameter on its own line at an indent of four, and the call in its default explodes from that column rather than from the column it occupied in the source.reflow-calls leaves the parameters of a signature that is about to explode alone, so nothing measures them twice, and the inner
inner_call_nmcall is measured against the exploded row with the buffer align-equals writes aroundkeyword_name =counted in.CONFIGis written as one overlong line with its keys out of order. The dict explodes to one entry per row, the keys sort, and every:pads into one column one space past the widest key,"beta_extended", so even the short"zeta"row takes the full padding to reach that column.tagdefswrites two spaces after each key's:, and alphabetize-siblings moves"stderr"ahead of"stdout", the comment above"stdout"moving with it. The"stdout"value, last after the sort, stays on one row at exactly the 40-column budget, because reflow-collections measures a value from the plain": "past its key rather than from the two spaces the source wrote, and the row is written back at that width, whereas the"stderr"value explodes.The module top carries
from __future__ import annotations, three aliased imports out of order, and three blank lines beforedef add. The directive is removed,collections,numpy, andrequestssort, theiraskeywords pad into one column, and the gap closes to two blank lines before the function.__all__lists six entries on one overlong line, three short names interleaved with three thirty-character ones. alphabetize-siblings sorts them so the short names lead, and reflow-collections packs them onto rows within the 88-column budget. The packing measures the widths the sort leaves, not the widths as written, which is why"cc"shares a row with the first long name rather than joining the two short entries above it, and the six entries settle onto three rows of two with no row widening afterward.Two tuples sit on one row that crosses the 88-column budget, one inside
isinstance(...)and one afterin. The tuple insideisinstancecomes first, so the overflow goes to that call, and the rest of the row counts at its full width rather than only up to the second tuple's(.isinstance(explodes withtoklistand(type(None), basestring, list)each on a row of their own, and(None, "", [])stays on one row, where it already fits, the tuple insideisinstancestaying packed as well.The dict passed to
self.call_exception_handlerlists'message','exception', and'loop', each with a trailing comma, and the sort moves'message'last. alphabetize-siblings settles which entry ends the dict, so reflow-collections measures each entry with the separator that order leaves after it, and the'message'entry, last after the sort, drops its comma, fits its row within the 79-column budget, and the layout settles once the sort is written.aa=lambda: g(111111, 2222, 333333)is the last keyword off, and alphabetize-siblings moves it ahead ofbb=1, which gives its row a trailing comma it did not carry while it was last. Theg(...)call inside the lambda explodes, because a nested call ending at the end of its row still counts whatever the enclosing row writes after it, and with the align-equals buffer around the=that comma crosses the 40-column budget.composearrives exploded, one parameter per line, with a trailing,afterverbose: bool. strip-trailing-commas removes that comma and neither rule changes anything else, because the stripped form is the layout reflow-signatures writes itself.Servicedeclares three fields and three methods, each set out of order. The fields sort and pad both their:and their=into one column each, the methods sort separately so_cleanupleadsrestartandshutdown, and space-statements writes one blank line between every member and one directly under theclass Service:header.self.loggerMapis assigned a dict with spaces just inside its braces, andma dict with no space after any:. strip-stranded-padding removes the spaces from the first and writes a space after each:in the second, and reflow-collections measures each literal at the width those edits leave, so the first stays on its row and only the second, now past the 40-column budget, explodes.The first element of the set
sis written across two lines as(1,and2). alphabetize-siblings sorts each element by the text it has once joined onto one row, not by the broken text in the source, so(1, 2)sorts after the long tuple.Sorting puts the long tuple first, which gives it a trailing comma, and measuring it with that comma takes it one column past the 40-column budget, so it explodes rather than staying on one row.
base.first(alpha,is hand-wrapped mid-call, withbeta).second(gamma).third(delta)on the next line. reflow-calls joinsfirst(alpha, beta)onto one row, which settles the chain's width before stack-method-chains counts its links, and the link count breaks the chain, wrapping the expression in parentheses and stacking.second(gamma)and.third(delta)under.first(alpha, beta).window(seen := len(rows), page_size, start, rows)passes a walrus as its first positional, and reflow-calls names itanchoras it explodes the call. A named expression used as a keyword value takes parentheses of its own, so the value opens one column further right than the plain=gap align-equals writes, and counting that column takes the row past the 30-column budget, so the nestedlencall explodes.SecureConnection.__init__callssuper(SecureConnection, self).__init__(...)withhost,port, andtimeoutwrapped one per line under the open parenthesis. reflow-calls joins the three onto one row, and shed-super-args then removesSecureConnectionandselffrom the call, leavingsuper().__init__(host, port, timeout), because the hand-aligned continuation lines are gone by the time the shortened call is measured.convertnamesAliasin its annotations whileAlias = intis written below it, underfrom __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.convertnamesSequencein its annotations whilefrom collections.abc import Sequenceis written below it, the case the directive exists for. band-constants moves the import above the definition, andfrom __future__ import annotationsis removed, because the relocated binding counts as written ahead of the annotations that name it and they resolve without the directive.render's docstring starts its summary on the"""line and carries anArgs:section. frame-docstrings moves the summary off the opener line, align-colons pads the:aftertemplate,context_map, andescape_htmlinto one column, and wrap-docstrings wraps the summary to the docstring budget across two lines.The
%tuple in theTypeErrorcall crosses the 60-column budget, withtype(other).__qualname__as its first element and two closing parentheses stacked at the end of its last row. reflow-collections explodes the tuple to one element per row with its)on a row of its own, and reflow-calls moves theTypeErrorcloser onto its own row as well. Thetype(other).__qualname__call stays on one line, because the explode movesself.__class__.__qualname__and the two closers off its row, andtype(other).__qualname__,alone fits the budget.dis a dict with# prose: keepat the end of its line, written with"b"before"a"and too wide for the 40-column budget. reflow-collections explodes the dict, which moves the marker onto the closing}line, and the marker still keeps the dict out of alphabetize-siblings, so"b"stays ahead of"a"and the entries keep their order and their separators, because the marker applies from the line of either the{or the}.process_env_var(tokenizer.read().text.replace(".", "_"))passes a method chain as its one positional. stack-method-chains wraps the chain in parentheses and stacks.text.replace(...)undertokenizer.read(), and reflow-calls then writes the argument in keyword form asenv_var = .... The parentheses stay around the value, because the value spans rows, and the stacked chain keeps its layout through the=alignment instead of being re-wrapped.f(bb=1, *args, aa=lambda: g(111111, 222222, 3333))puts*argsbetween two keywords. A keyword sort run is bounded by a**unpacking and not by a positional, so*argssits inside one run rather than splitting it in two. reflow-calls measuresaa, the keyword the sort would move first, with the comma it would gain, which takes it past the 40-column budget, so itslambdavalue explodes, and once a value spans rows the sort leaves the group in place,*argsstaying exactly where it was written._filter(flist=os.listdir(self.left_folders), skip=self.hide)crosses the 40-column budget. reflow-calls puts each keyword on its own row, and align-equals writes a buffer around each=, which moves every value two columns right. The nestedos.listdir(...)call explodes, because it is measured from that settled column rather than from where it sat before the buffer, and those two columns take it past the budget._FORMATSlistsFMT_BINARYandFMT_XMLin the order alphabetize-siblings leaves them, so the last entry gains no separator, and align-colons padsFMT_XMLonto a:column that puts its row exactly at the 45-column budget. The column stays, because reflow-collections measures that row at exactly the budget rather than past it, and the padding is not stripped back.non_adjacent.append( (la, lb, 0) )carries a space afterappend(and a space before its closing), and the row crosses the 40-column budget by exactly those two spaces. strip-stranded-padding removes both, and reflow-collections measures the tuple at the width that removal leaves, so the tuple stays on its row, the first space narrowing the column the tuple starts at and the second narrowing the text trailing it.LIMIT = 3sits betweenfrom pkg import alphaandfrom pkg import beta, which would ordinarily keep the two apart. band-constants moves the constant below the imports, and reflow-imports merges the twopkgstatements 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.Three methods of
Dispatchersit out of order, andhandle_alphacontains amatchwhose arms each return a single value. The methods sort sohandle_alphaleads, align-match-case joins eachreturnonto itscaseline and pads the:into one column, and space-statements writes one blank line between the methods.zeta,alpha, andbetaarrive out of order,alphawith a two-line docstring and the other two with one-line docstrings. alphabetize-siblings sorts the methods, expand-docstrings rewrites the one-line docstrings ofbetaandzetato multi-line form, and frame-docstrings puts every"""on its own line, with space-statements writing one blank line between the methods.pairedlists three entries with uneven spacing before their:, andsinglelists one entry written"only_key" : 1. align-colons padspaired's:into one column one space past"second", and strip-stranded-padding removes the stray space insingle, leaving"only_key": 1, because a lone entry forms no alignment group.self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))passes two calls joined by+, and the row crosses the 40-column budget._flatten(explodes directly, because reflow-calls measures it with the+ self._options(that trails it on the same row counted in, whereas measuring the call alone would have left the row for reflow-collections to break at the tuple first and the call would then have exploded on a later pass anyway.freturns an outer chain whose first link takes an inner chain as its argument, on a row past the 60-column budget. stack-method-chains wraps the outer chain in parentheses, and the row holdingo.first_link_that_is_quite_long(still crosses the budget, so reflow-calls explodes that argument list. The inner chain stays joined on one line, because it is measured from the column it ends up at, one indent step past that row, rather than from the wider column the joined row would have given it, and it fits there.PRIMARYoverflows its line with values written in the olderOptionalandUnionforms, andtarget-version = "3.10"allows the|syntax. modernize-annotations rewrites each value to|form first, so every rule downstream measures the shorter text, and the dict then explodes, its keys sort so"delta_long"moves ahead of"gamma", and align-colons pads the:column against the rewritten widths. With nothing left reading either name,from typing import Optional, Unionis removed.dopens with{ # noteand lists"zz"before"a": 1, "b": 2packed on one row. The comment on the{row keeps reflow-collections from splitting that row, and a packed row is never reordered, so the dict keeps its source order under alphabetize-siblings. The"zz"entry is measured with the comma it carries as written rather than one a sort would have moved, its value is too wide for the 40-column budget, so that value explodes while"a": 1, "b": 2stay packed on their shared row.sis a set packed across three rows with# noteat the end of the first. The comment inside the set keeps its order fixed under alphabetize-siblings, so no element moves, and reflow-collections measures each element with the comma it carries as written rather than the one a sort would have left. The one element too wide for the 40-column budget explodes onto rows of its own, and the packed rows around it keep their commas and their places.Both
Attributes:entries ofProcessrun past the docstring budget before anything wraps them. align-colons pads both:to one column pastcommand_messagewith no line cap of its own, and wrap-docstrings then reflows each description withindocstring-line-lengthand hangs the wrapped lines from that padded column, so both entries reach their final layout in one pass rather than drifting across several.dirlist.extend([ ... ])writes a space just inside each bracket of its list, and strip-stranded-padding reports both. reflow-collections explodes the list, rebuilding the bracket interior and carrying that padding away before the strip is written, and the first member call ends up alone on a row inside the exploded list with no padding ahead of it. The_os.path.expanduser(...)call is measured from that row and explodes, because at that column it crosses the 60-column budget, rather than being measured from the row the source wrote._creturns two calls joined byand, each wrapped in a redundant grouping pair, on a row past the 60-column budget. reflow-calls explodes the first call and puts its closing)back at the statement's indent, and reflow-parentheses measures the second pair from the column that closer leaves, where it fits, so the pair's interior joins onto that row and both pairs drop, rather than the second staying wrapped for a width the explode has already removed.totalis assigned a%template whose tuple arrives exploded,firstandsecondeach on their own line. reflow-collections condenses the tuple onto one line first, and prefer-fstring then measures the conversion against the budget and rewrites"%s and %s" % (first, second)to `f"{first} and {second}", each member inline in the template.dispatchopens with the assignmentsx,yz, andqrsabove amatchwhose arms each return one of those bindings. align-equals pads the three=into one column one space pastqrs, and align-match-case joins each singlereturnonto itscaseline with every arm:padded one space past the widest pattern,"alpha".specsis assigned a tuple whose last element istuple(_mapdict_values(statemap)), on a row past the 40-column budget. reflow-calls leaves a literal that reflow-collections is about to explode unwalked, so reflow-collections explodes the outer tuple and lays out thetuple(and_mapdict_values(calls inside it against the rows the explode leaves. Both calls explode at the column they end up at, because at that deeper column neither fits the budget.The
%tuple inwarningends with aself.userCfg[configType].Get(...)call, and exploding the tuple moves that final item four columns right, past the 79-column budget. reflow-collections reshapes the reflow-calls layout of that call against the row it ends up on, so the call explodes in the same run as the move, rather than on a later pass.sup.__init__(option_strings=[], dest=dest, help=help, metavar=metavar)is written across two rows, and sorting the keywords produces no row wider than the widest already in the source, so alphabetize-siblings reorders them. align-equals then writes a buffer around the=ofoption_strings, the keyword left alone on its row, and that buffer does not trigger a second width check, because the reorder was already measured against the source's own widest row.Inside
ensure_running, thecmd %= (...)statement spans two rows betweenfds_to_passandmain_kws["authkey_r"]above it andexeandargsbelow. align-equals builds its runs the same way when it reserves as when it aligns, so the multi-row statement closes the run above it, andexeandargspad onto a narrow column of their own rather than the wider onefds_to_passandmain_kws["authkey_r"]set. reflow-collections reads that same run boundary and joins the tuple within the 80-column budget, rather than exploding it from a column the aligner never uses.handler_map's return annotation crosses the 50-column budget, and the only collection literal on that row is the[Values, list[str]]list insideCallable. reflow-collections explodes that literal, and reflow-signatures keepsselfinline, because it reads the opening row as ending at the literal's bracket rather than exploding a lone parameter to recover width the explode is about to give back.Bare, external
from, and local imports sit scrambled together, withmyappnamed infirst-party. group-imports partitions them into bare, external, and local sections, alphabetize-siblings sorts the names within each section, space-statements writes one blank line between sections, and align-imports pads theimportkeyword into a column within each section.yis a four-element set on one line past the 30-column budget. reflow-collections packs the rows at the widths alphabetize-siblings leaves in each slot, not the widths the source wrote, so the rows fill the budget as densely as a list's would and the sort is written without widening any row.add_exceptioncallssuper(Waiter, self).add_exception(future)on a row past the 40-column budget. shed-super-args runs ahead of reflow-calls, so the baresuper()is the text the length trigger measures, and theadd_exceptioncall explodes on that width rather than on the wider text the shed removes.storewritesttl : intwith a space before the:, and its one-line signature crosses the 40-column budget by exactly that space. strip-stranded-padding removes it, and reflow-signatures measures the signature at the width that removal leaves, so it stays on one line rather than exploding and joining back on a later pass.The one
Args:entry offetchis writtentimeout : Maximum seconds to wait before giving up., with a space before its:, and its description runs past the 40-column docstring budget. strip-stranded-padding removes the space, because a lone entry has no sibling column to share, and wrap-docstrings breaks the description at the budget and hangs the continuation under the start of the text as the strip leaves it, one column narrower than the source, so both rules agree on where the continuation sits.tablecarries# prose: skip[alphabetize-siblings]on its closing line, listing"bb"before"aa". reflow-collections ordinarily measures each entry with the comma the sort is about to write, and the skip directive turns the sort off, and with it the measurement against the sorted order, so every entry is measured with the comma it already carries, and"bb"counts a trailing comma and breaks after its:rather than being measured as the last entry.foo({"alpha": 1, "beta": 2, "gamma": 3, "delta": 4})passes one dict as its only argument. reflow-collections explodes the dict to one entry per row, and the call explodes around the dict, its(and{on separate rows and its}and)on separate rows, because an argument that spans rows explodes the call whatever the argument count and however short the one-line form. Inside, alphabetize-siblings sorts the keys so"delta"moves ahead of"gamma", and align-colons pads each:into one column.screen._drawpoly(...)is a method call whose signature reflow-calls cannot resolve, so it leaves the positionals unnamed and explodes the arguments in source order. Each keyword value is still measured from the column the align-equals buffer puts it at, with the comma that closes its row counted in, and those two together take theoutlinevalue past the 40-column budget, so it explodes, whilefillandtopfit their rows andwidth = self._outlinewidth, an attribute access with nothing to explode, stays on its row past the budget.out.write(...)takes a chain of two.replacecalls onrepr(value), already stacked one link per line. stack-method-chains wraps the chain in parentheses and puts the receiver and its first link together on the row below the opener, and that row, holdingrepr(value).replace(, crosses the 88-column budget. reflow-calls explodes that link against the row it sits on rather than against the pair's opener, its two string arguments one indent step in from that row and its)back at that row's indent, while the second.replacekeeps its place in the.column below.retries,timeout_seconds, andwindoware annotated assignments with trailing comments, and three alignment rules pad the same rows, each checking its own padding against the 72-column budget. Every rule measures the row's settled form rather than the intermediate text the rule before it left, which is what lets the:, the=, and the#all reach their columns with the widest row,retries, still inside the budget.zebra,alpha, andmangosit out of alphabetical order at module level with no spacing between them. alphabetize-siblings sorts the definitions and space-statements writes two blank lines between each pair of top-level functions.configure(...)carries acoordskeyword whose tuple of three calls is too wide for one line. reflow-collections explodes the tuple and reflow-calls re-indents its elements under the keyword, alphabetize-siblings sortsmore,name, andother, and align-equals pads each=into one column.coordskeeps its source position, because its tuple calls functions such ascompute_alpha_coordinate()and an entry whose value runs code is never moved, and the tuple's elements sit one indent step deeper than the keyword rows with its)back at the keyword column, as alistordictargument would.configuretakes five typed parameters on one line, two with defaults. reflow-signatures explodes the signature to one parameter per line, align-colons pads each name to one:column, and align-equals pads the=ofmangoanddeltainto one column, while the parameters keep their source order even with alphabetize-siblings in the run, because reordering parameters would change every positional call.from pkg import aandfrom pkg import bare both unread, andais the line the# Local importscomment heads.from pkg import bcarries no comment and is removed, and with it gone there is no sibling left for reflow-imports to mergeainto, sofrom pkg import astays under its comment rather than being removed too, because removing it would leave the comment over nothing.from pkg import ais never read and is the line the# Local importscomment heads, withX = 1between it andfrom pkg import b. band-constants movesX = 1below the imports, which brings the twopkgstatements together, and with them adjacentfrom pkg import bmoves up onto the lineavacates asais removed, leaving the comment heading a real import rather than a gap.configure's signature arrives exploded, its four typed parameters carrying defaults at widths fromhosttotimeout_seconds. align-colons pads every name to one:column set bytimeout_seconds, and align-equals pads every annotation to one=column set byfloat, so all four rows read as name, type, default.
How Composition Resolves
Each case runs its listed rules in canonical order, and the sections below cover the common interactions.
Layout Before Alignment
reflow-collections runs before align-colons and writes the one-entry-per-line layout the alignment columns are computed from.
Reorder Before Align
alphabetize-siblings runs before align-equals and settles the entry order, so the alignment math measures the final column positions rather than the source ones.
Docstring Discipline Before Wrap
expand-docstrings and frame-docstrings run before wrap-docstrings and settle the quote placement before the body rewrap measures its budget.
Module Reorder Around a Block Marker
band-constants sorts the constants above a # fmt: off block while the lines inside the block stay as written, so both it and align-equals act freely outside the marked region.
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.
bar_baz = 3
foo = 2
zebra = 1
# fmt: off
matrix = [[0.7, 0.1, 0.1],
[0.1, 0.7, 0.1],
[0.1, 0.1, 0.7]]
# fmt: on
Click any rule chip above for its canonical case. The Pipeline Order reference lists the order the pipeline runs in, the Pipeline primitive covers the runner, and the Rules catalog lists the rest.