This fork enhances the original blank-line-after-blocks tool. It introduces
command-line options (--after, --not-after, --compound) for granular
control over block formatting and extends support to def, class, match
statements, and docstrings.
Breaking Changes:
- The default behavior now adds blank lines after
docstring,def,class, andmatchblocks, in addition to the originalif,for,while,with, andtryblocks. - The default behavior for compound statements (like
if/elif/else) has changed to add blank lines between each part for consistent spacing.
To restore the original behavior, use the following command:
blank-line-after --not-after docstring,def,class,match --compound elif,else,except,finally <files>A Python formatter to automatically add blank lines after code blocks to
improve readability. Fully customizable with --after, --not-after, and
--compound options.
Table of Contents
pip install blank-line-after# Format Python files (default: adds blank lines after if/for/while/with/try)
blank-line-after file1.py file2.py
# Format Jupyter notebooks
blank-line-after-jupyter notebook1.ipynb notebook2.ipynb
# Format with exclude patterns (regex - use | for multiple patterns)
blank-line-after --exclude "tests/|_generated\.py$" src/Control which blocks get blank lines and how compound statements are formatted:
# Only add blank lines after specific block types
blank-line-after --after if,for file.py
blank-line-after --after def,class file.py
# Add blank lines after ALL blocks EXCEPT specified ones
blank-line-after --not-after if,for file.py
# Control compound statement spacing (default: consistent spacing everywhere)
# Use --compound to keep certain compound statements tight
blank-line-after --compound elif,else file.py
blank-line-after --compound except,finally file.py
# Combine options (--after/--not-after are mutually exclusive)
blank-line-after --after def,class --compound elif,else file.pyAvailable block types for --after/--not-after:
if,for,while,with,try(default blocks, includesasync forandasync with)def(functions, includesasync def),class(classes)match(Python 3.10+ match statements)docstring(module/function/class docstrings)
Available compound headers for --compound:
elif,else,except,finally,case
Add this to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/jsh9/blank-line-after
rev: <LATEST_TAG>
hooks:
- id: blank-line-after
- id: blank-line-after-jupyterYou can also pass customization options to pre-commit hooks:
repos:
- repo: https://github.com/jsh9/blank-line-after
rev: <LATEST_TAG>
hooks:
- id: blank-line-after
args: ["--after", "def,class", "--compound", "elif,else"]
- id: blank-line-after
args: ["--exclude", r"tests/|_generated\.py$"]
- id: blank-line-after-jupyter
args: ["--exclude", "notebooks/generated/"]You can also configure exclude patterns in pyproject.toml:
[tool.blank-line-after]
exclude = [
"tests/", # Exclude all files in tests directory
"_generated\.py$", # Exclude files ending with _generated.py
"vendor/", # Exclude all files in vendor directory
"build/", # Exclude build directory
]Note: CLI --exclude options take precedence over configuration file
settings.
By default, this tool adds one blank line after the end of if, for,
while, with, and try blocks to improve code readability.
Key features:
- Customizable: Use
--afterto specify exactly which blocks to format - Flexible: Use
--not-afterto format all blocks except specific ones - Consistent spacing: By default, adds blank lines after each branch in compound statements (if/elif/else, try/except/finally) for consistency
- Compact mode: Use
--compoundto keep compound statements tightly grouped
The tool supports these block types:
- Control flow:
if,for,while,with,try - Definitions:
def(functions),class(classes) - Pattern matching:
match(Python 3.10+) - Documentation:
docstring(module/function/class docstrings)
Default adds blank lines after if, for, while, with, try blocks with
consistent spacing (blank lines after each branch):
if a == 'a':
depth += 1
+
elif b == 'b':
depth -= 1
+
else:
depth = 0
+
j = 1 for item in items:
process(item)
+
final_step() try:
risky()
+
except ValueError:
handle()
+
finally:
cleanup()
+
done()Only add blank lines after specific block types:
blank-line-after --after def,class file.py if x > 5:
print("hello")
def my_func():
return 42
+
class MyClass:
pass
+
print("world")Add blank lines after ALL blocks EXCEPT the specified ones:
blank-line-after --not-after if,for file.py if x > 5:
print("hello")
def my_func():
return 42
+
for i in range(10):
print(i)
while True:
break
+
print("world")Keep compound statements tightly grouped:
blank-line-after --compound elif,else file.py if a == 'a':
depth += 1
elif b == 'b':
depth -= 1
else:
depth = 0
+
j = 1blank-line-after --compound except,finally file.py try:
risky()
except ValueError:
handle()
finally:
cleanup()
+
done()blank-line-after --after docstring file.py def my_func():
"""Function docstring."""
+
return 42
class MyClass:
"""Class docstring."""
+
def method(self):
passDefault behavior adds blank lines after each case block:
match status:
case 200:
success()
+
case 404:
not_found()
+
case _:
default()
+
done()Keep case blocks tightly grouped with --compound case:
blank-line-after --compound case file.py match status:
case 200:
success()
case 404:
not_found()
case _:
default()
+
done()Async variants of for and with are automatically supported:
async def process_items():
async for item in async_iterator:
await handle(item)
+
async with aiofiles.open("file.txt") as f:
content = await f.read()
+
done()