Skip to content

Section layout

Section layout

pyreorder classifies every top-level statement into a section, then emits sections in a fixed order. Within a section, statements are ordered by the section's strategy. The result is a predictable, readable module shape: imports first, constants before classes, helpers near the bottom, and the main guard last.

How statements are classified

Section Matches
imports import, from ... import (except from __future__)
typing_imports if TYPE_CHECKING: block
module_constants assignments to ALL_CAPS or dunder (__all__, __version__)
runtime_setup module-level assignments to non-constant names (logger = ..., app = ...)
enums classes whose base ends in Enum / Flag
dataclasses classes decorated @dataclass
classes other class definitions
functions top-level def
dunder_exports assignments to configured dunder names (__all__ by default)
main_block if __name__ == "__main__":
other (barrier) anything else — never moved

A statement whose section is not listed in module.sections is also treated as a barrier and kept in place.

Default order

[tool.pyreorder.module]
sections = [
    "imports",
    "typing_imports",
    "module_constants",
    "runtime_setup",
    "enums",
    "dataclasses",
    "classes",
    "functions",
    "dunder_exports",
    "main_block",
]

Worked example: constants above classes

Before:

class Service:
    def start(self):
        ...

MAX_CONN = 10
API_URL = "https://example.com"

import os

After pyreorder run (default keep strategy):

import os

MAX_CONN = 10
API_URL = "https://example.com"

class Service:
    def start(self):
        ...

Imports float to the top, module constants sit above the class, and the class moves below the constants — exactly the "constants above classes" ordering the layout guarantees.

Pinned statements

Two things are never reordered, even if they appear out of place:

  • the module docstring, and
  • from __future__ import ... statements (these must stay at the very top of the module to be valid Python).

Barriers

Statements pyreorder does not recognise — runtime setup such as app = typer.Typer() or a module-level setup() call — become barriers. Recognised statements only reorder within the contiguous run between barriers, so pyreorder never moves code across a statement it might depend on.

Before (a barrier splits the functions):

def bootstrap():
    ...

app = typer.Typer()          # barrier: not a recognised section

def handler():
    ...

def helper():
    ...

After: bootstrap stays above the barrier; handler and helper reorder among themselves below it, but nothing crosses the app = ... line.

def bootstrap():
    ...

app = typer.Typer()          # barrier: unchanged, never crossed

def handler():
    ...

def helper():
    ...

Use barriers (or a # pyreorder: off directive) whenever a top-level statement has order-dependent side effects.

Realistic examples

Five fully-formed sample modules live in tests/data/, each with an unsorted input and committed sorted output. They demonstrate the section layout, barriers, and strategies on real-world Python patterns:

pyreorder diff tests/data/web_service_unsorted.py     # stepdown + undersort
pyreorder diff tests/data/cli_app_unsorted.py          # the Typer barrier pattern
pyreorder diff tests/data/inventory_models_unsorted.py # alpha + rich undersort

See Sorting modes for the full table of what each sample demonstrates.