Structural Formatting
Structural formatting rules control the layout of Python constructs beyond basic whitespace and indentation.
Trailing Commas
Trailing commas in multi-line structures improve git diffs and make adding items easier.
Configuration
Controlled by beacon.formatting.trailingCommas:
always: Add trailing commas to all multi-line structuresmultiline: Add trailing commas only to multi-line nested structures (default)never: Never add trailing commas
Behavior
The formatter determines whether to add a trailing comma based on:
- The trailing comma configuration setting
- Whether the structure spans multiple lines
- The nesting depth of the structure
For multiline mode, trailing commas are added when both conditions are met:
- The structure is multi-line
- The structure is nested (inside parentheses, brackets, or braces)
Examples
# multiline mode
items = [
"first",
"second",
"third", # trailing comma added (nested and multiline)
]
func(
arg1,
arg2, # trailing comma added
)
# Top-level, single-line: no trailing comma
top_level = ["a", "b", "c"]
Dictionary Formatting
Dictionary formatting includes key-value spacing and multi-line alignment.
Value Indentation
For multi-line dictionaries, the formatter calculates appropriate indentation:
- Nested dictionaries: Use base indentation + 1 level
- Inline dictionaries: Align values with key width + 2 spaces
# Nested multi-line
config = {
"key": "value",
"nested": {
"inner": "data",
},
}
# Inline alignment
options = {"short": "val", "longer_key": "val"}
Comprehensions
List, dict, set, and generator comprehensions are formatted based on length.
Wrapping Strategy
The formatter chooses between horizontal and vertical layout:
- Horizontal: Entire comprehension fits on one line
- Vertical: Comprehension exceeds available line space
# Horizontal (fits on one line)
squares = [x**2 for x in range(10)]
# Vertical (too long)
result = [
transform(item)
for item in collection
if predicate(item)
]
Lambda Expressions
Lambda expressions wrap to multiple lines when they exceed the line length limit.
Wrapping Decision
Determined by: current_column + lambda_width > line_length
# Short lambda: stays on one line
square = lambda x: x**2
# Long lambda: may need refactoring to def
complex = lambda x, y, z: (
some_complex_calculation(x, y, z)
)
Decorators
Decorators are formatted with one decorator per line and proper spacing.
Rules
- Each decorator on its own line
- Decorators aligned at the same indentation as the function/class
- @ symbol normalized (added if missing)
- No blank lines between consecutive decorators
@property
@lru_cache(maxsize=128)
def expensive_computation(self):
return result
Class Definitions
Class definitions follow PEP 8 spacing conventions.
Blank Lines
Controlled by beacon.formatting.blankLineBeforeClass:
- Top-level classes: 2 blank lines before (when enabled)
- Nested classes: 1 blank line before
# Module-level
class TopLevelClass:
pass
class AnotherTopLevelClass:
class NestedClass:
pass
Function Definitions
Function definitions use similar spacing rules to classes.
Blank Lines
Controlled by beacon.formatting.blankLineBeforeFunction:
- Top-level functions: 2 blank lines before (when enabled)
- Methods: 1 blank line before
# Module-level
def top_level_function():
pass
class MyClass:
def method_one(self):
pass
def method_two(self):
pass
Type Annotations
Type annotation spacing follows PEP 8 guidelines.
Spacing Rules
Returns tuple (before, after) for colon spacing:
- Variable annotations: No space before colon, one space after
- Return annotations: Space before and after
->
# Variable annotations
name: str = "value"
count: int = 42
# Function annotations
def greet(name: str, age: int) -> str:
return f"Hello {name}"
Implementation
Structural formatting rules are implemented in FormattingRules:
should_add_trailing_comma(): Determines trailing comma insertionformat_decorator(): Normalizes decorator syntaxtype_annotation_spacing(): Returns spacing tupleshould_wrap_lambda(): Decides lambda wrappingdict_value_indent(): Calculates dictionary value indentationcomprehension_wrapping_strategy(): Returns wrapping strategyblank_lines_for_class(): Returns required blank lines for classesblank_lines_for_function(): Returns required blank lines for functions
All rules are context-aware and respect the current indentation level and nesting depth.