https://github.com/zigai/rattle-blank-lines
Python lint rules for blank-line and statement cuddling
https://github.com/zigai/rattle-blank-lines
blank-line linter python python3 rattle
Last synced: 3 months ago
JSON representation
Python lint rules for blank-line and statement cuddling
- Host: GitHub
- URL: https://github.com/zigai/rattle-blank-lines
- Owner: zigai
- License: mit
- Created: 2026-02-27T09:47:51.000Z (4 months ago)
- Default Branch: master
- Last Pushed: 2026-03-28T15:04:13.000Z (3 months ago)
- Last Synced: 2026-03-28T15:54:47.456Z (3 months ago)
- Topics: blank-line, linter, python, python3, rattle
- Language: Python
- Homepage:
- Size: 205 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# rattle-blank-lines
[](https://github.com/zigai/rattle-blank-lines/actions/workflows/tests.yml)
[](https://badge.fury.io/py/rattle-blank-lines)

[](https://pepy.tech/project/rattle-blank-lines)
[](https://github.com/zigai/rattle-blank-lines/blob/master/LICENSE)
[Rattle](https://github.com/zigai/rattle) rules for blank-line and statement-cuddling policy checks in Python.
## Installation
```sh
pip install rattle-blank-lines
```
```sh
uv add rattle-blank-lines
```
## Quick Start
Add the rule pack to your project configuration:
```toml
[tool.rattle]
root = true
enable = ["rattle_blank_lines.rules"]
```
This adds the `rattle_blank_lines` rules.
Rattle's built-in `rattle.rules` stay enabled unless you disable them.
If you want to run only `rattle_blank_lines`, also set `disable = ["rattle.rules"]`.
Run linting and autofix:
```sh
rattle lint
rattle lint --diff
rattle fix
```
For in-file suppressions, use Rattle comments:
- `# lint-ignore: BlankLineBeforeAssignment`
- `# lint-fixme: BlankLineBeforeAssignment`
## Rules
### NoSuiteLeadingTrailingBlankLines (BL100, BL101)
Removes leading and trailing blank lines at suite boundaries.
Before:
```python
def f() -> int:
value = 1
return value
```
After:
```python
def f() -> int:
value = 1
return value
```
### BlankLineBeforeBranchInLargeSuite (BL200)
Requires a blank line before `return`/`raise`/`break`/`continue` in larger suites.
Before:
```python
def f(value: int) -> int:
x = value + 1
y = x + 1
z = y + 1
return z
```
After:
```python
def f(value: int) -> int:
x = value + 1
y = x + 1
z = y + 1
return z
```
### BlockHeaderCuddleRelaxed (BL300)
Allows cuddling before a block when the setup still belongs to the same step.
The first statement after a suite docstring is exempt.
Before:
```python
def f(value: int) -> int:
prepared = value + 1
if value > 0:
return value
return 0
```
After:
```python
def f(value: int) -> int:
prepared = value + 1
if value > 0:
return value
return 0
```
Also allowed:
```python
def f(override_name: str | None) -> str:
display_name = "guest"
if override_name is not None:
display_name = override_name
return display_name
```
```python
def f(slots: dict[str, int], key: str) -> None:
slots[key] -= 1
if slots[key] < 0:
raise ValueError(key)
```
### BlockHeaderCuddleStrict (BL301)
Stricter cuddle mode. The first statement after a suite docstring is exempt.
Opt in with `rattle_blank_lines.rules.block_header_cuddle_strict:BlockHeaderCuddleStrict`, and disable `BlockHeaderCuddleRelaxed` if you want BL301 instead of BL300.
```toml
[tool.rattle]
root = true
enable = [
"rattle_blank_lines.rules",
"rattle_blank_lines.rules.block_header_cuddle_strict:BlockHeaderCuddleStrict",
]
disable = [
"BlockHeaderCuddleRelaxed",
]
```
Before:
```python
def f(value: int) -> int:
header_value = value + 1
trailing = value + 2
if header_value > 0:
return header_value
return 0
```
After:
```python
def f(value: int) -> int:
header_value = value + 1
trailing = value + 2
if header_value > 0:
return header_value
return 0
```
### BlankLineAfterControlBlock (BL350)
Requires a blank line after multiline control-flow blocks.
Some compact patterns stay together, such as guard ladders, `with pytest.raises(...)` clusters, and immediate inspection after `with`.
Before:
```python
def f(value: int) -> int:
if value > 0:
value += 1
return value
```
After:
```python
def f(value: int) -> int:
if value > 0:
value += 1
return value
```
### BlankLineBeforeAssignment (BL210)
Requires a blank line before an assignment after a non-assignment statement.
Before:
```python
def f(candidate: object) -> object:
validate(candidate)
display_value = str(candidate)
if supports_live_interaction():
highlight(candidate)
return candidate
```
Usually:
```python
def f(candidate: object) -> object:
validate(candidate)
display_value = str(candidate)
if supports_live_interaction():
highlight(candidate)
return candidate
```
Also allowed:
```python
def f() -> int:
log_start()
value = compute()
return value
```
Also allowed:
```python
def configure_logger(logger: logging.Logger, handler: logging.Handler) -> None:
logger.addHandler(handler)
logger.propagate = False
```
### MatchCaseSeparation (BL400)
Requires a blank line before the next `case` after a larger case body.
This rule is opt-in and is not included by `enable = ["rattle_blank_lines.rules"]`.
You can enable it with `enable = ["rattle_blank_lines.rules.match_case_separation:MatchCaseSeparation"]`.
Before:
```python
def f(value: int) -> int:
match value:
case 1:
a = 1
b = 2
c = 3
case _:
return 0
```
## Rule Options
```toml
[tool.rattle.options]
[tool.rattle.options.BlankLineBeforeBranchInLargeSuite]
max_suite_non_empty_lines = 2
compact_tail_max_statements = 2
allow_related_return_tails = true
allow_guard_ladder_final_branch = true
[tool.rattle.options.BlankLineBeforeAssignment]
short_control_flow_max_statements = 3
related_use_lookahead = 2
allow_local_helper_capture = true
allow_post_guard_continuation = true
[tool.rattle.options.BlockHeaderCuddleRelaxed]
body_usage_lookahead = 4
setup_run_lookback = 3
allow_setup_before_compact_guard_ladder = true
[tool.rattle.options.BlankLineAfterControlBlock]
related_use_lookahead = 2
allow_compact_guard_ladders = true
allow_pytest_raises_clusters = true
allow_with_immediate_inspection = true
[tool.rattle.options.MatchCaseSeparation]
max_case_non_empty_lines = 2
```
After:
```python
def f(value: int) -> int:
match value:
case 1:
a = 1
b = 2
c = 3
case _:
return 0
```
## License
[MIT](https://github.com/zigai/rattle-blank-lines/LICENSE)