Python blank line formatter enforcing spacing rules for clean, readable code
Project description
Spacing
A Python code formatter that enforces configurable blank line rules.
Overview
Spacing is a code formatting tool that intelligently manages blank lines in Python code, similar to how black handles code formatting. It applies sophisticated rules to ensure consistent spacing between different types of code blocks while preserving your code's logical structure and documentation.
Features
- Configurable blank line rules - Customize spacing between different code block types
- Smart block detection - Identifies assignments, function calls, imports, control structures, definitions, and more
- Multiline statement support - Properly handles statements spanning multiple lines
- Docstring preservation - Never modifies content within docstrings
- Nested scope handling - Applies rules independently at each indentation level
- Comment-aware processing - Preserves existing spacing around comment blocks
- Atomic file operations - Safe file writing with automatic rollback on errors
- Change detection - Only modifies files that need formatting
- Dry-run mode - Preview changes without modifying files
- Check mode - Verify formatting without making changes
Installation
From PyPI
pip install spacing
From Source
git clone git@gitlab.com:oldmission/spacing.git
cd spacing
pip install -e .
Requirements
- Python 3.11 or higher
- No external dependencies for core functionality
Quick Start
# Format all Python files in current directory (automatic discovery with smart exclusions)
spacing
# Format a single file
spacing myfile.py
# Format all Python files in a specific directory
spacing src/
# Check if files need formatting (exit 1 if changes needed)
spacing --check
# Preview changes without applying them
spacing --dry-run
# Show detailed processing information
spacing --verbose
# Show version
spacing --version
Automatic File Discovery
When you run spacing without any path arguments, it automatically:
- Discovers all
.pyfiles in the current directory (recursively) - Excludes common directories you don't want to format:
- Hidden directories (starting with
.) - Virtual environments (
venv,env,virtualenv) - Build artifacts (
build,dist,__pycache__,*.egg-info,*.egg)
- Hidden directories (starting with
- Respects custom exclusions defined in
spacing.toml(see Configuration section below)
Configuration
Default Behavior
By default, spacing uses these rules (aligned with PEP 8):
- 1 blank line between different block types
- 1 blank line between consecutive control structures (
if,for,while,try, etc.) - 2 blank lines between consecutive top-level (module level) function/class definitions
- 1 blank line between consecutive method definitions inside classes
- 0 blank lines between statements of the same type
- Exception: 1 blank line between consecutive control blocks at the same scope
Configuration File
Create a spacing.toml file in your project root to customize blank line rules:
[blank_lines]
# Default spacing between different block types (0-3 blank lines)
default_between_different = 1
# Spacing between consecutive control blocks (if, for, while, try, with)
consecutive_control = 1
# Spacing between consecutive definitions (def, class)
consecutive_definition = 1
# Indent width for indentation detection (default: 2 spaces)
indent_width = 2
# Fine-grained transition overrides
# Format: <from_block>_to_<to_block> = <count>
assignment_to_call = 2
call_to_assignment = 2
import_to_assignment = 0
control_to_definition = 2
[paths]
# Additional directory/file names to exclude during automatic discovery
exclude_names = ["my_generated_code", "legacy"]
# Glob patterns for more specific exclusions
exclude_patterns = ["**/old_*.py", "**/test_old_*.py"]
# Set to true to include hidden directories (overrides default exclusion)
include_hidden = false
Note: Path exclusions only apply when running spacing without arguments (automatic discovery). They are ignored when you explicitly specify paths like spacing src/ or spacing venv/ - this gives you full control when needed.
Block Types
Spacing recognizes these code block types (in precedence order):
-
assignment- Variable assignments, list/dict comprehensions, lambda expressionsx = 42 items = [i for i in range(10)] func = lambda x: x * 2
-
call- Function/method calls,del,assert,pass,raise,yield,returnprint('hello') someFunction() return result
-
import- Import statementsimport os from pathlib import Path
-
control- Control structures (if/elif/else,for/else,while/else,try/except/finally,with)if condition: x = 1 y = 0 for item in items: prologue(item) process(item) epilogue(item)
-
definition- Function and class definitionsdef myFunction(): pass class MyClass: pass
-
declaration-globalandnonlocalstatementsglobal myVar nonlocal count
-
comment- Comment lines# This is a comment
Configuration Examples
Minimal spacing (compact style)
[blank_lines]
default_between_different = 0
consecutive_control = 1
consecutive_definition = 1
Extra spacing (airy style)
[blank_lines]
default_between_different = 2
consecutive_control = 2
consecutive_definition = 2
Custom transitions
[blank_lines]
# Default: 1 blank line between different types
default_between_different = 1
# But no blank lines between imports and assignments
import_to_assignment = 0
# And 2 blank lines between import blocks and definitions such as a `class`
import_to_definition = 2
Using Custom Configuration
# Use a specific config file
spacing --config custom.toml myfile.py
# Use default configuration (ignore spacing.toml if it exists)
spacing --no-config myfile.py
Block Classification Rules
Precedence
When a statement could match multiple block types, spacing uses precedence:
x = someFunction() # Assignment (precedence over Call)
result = [i for i in range(10)] # Assignment (comprehension)
Multiline Statements
Multiline statements are classified as a single unit:
result = complexFunction(
arg1,
arg2,
arg3
) # Entire statement is classified as Assignment
Docstrings
Docstring content is never modified - all internal formatting, blank lines, and special characters are preserved exactly:
def example():
"""
This docstring content is preserved exactly.
# This is NOT treated as a comment
All blank lines inside are preserved.
"""
pass
Comment Handling
Spacing has special rules for comments:
-
Consecutive comments - No blank lines inserted between comment lines
# Copyright header line 1 # Copyright header line 2 # Copyright header line 3
-
Comment breaks - Blank line added before a comment (unless previous line was also a comment)
x = 1 # This comment gets a blank line before it y = 2
-
After comments - Existing spacing preserved (leave-as-is policy)
# Comment import os # Existing blank line preserved # Comment x = 1 # No blank line (preserved)
Scope and Blank Lines
Spacing applies rules independent of scope:
def outer():
x = 1
y = 0
z = 0
print('Level 1')
def inner():
y += 1
print('Level 2')
if condition:
z += 1
Rules are applied separately for:
- Module level (indent 0)
- Inside
outer()function (indent 2) - Inside
inner()function (indent 4) - Inside
ifblock (indent 6)
Exit Codes
- 0 - Success: No changes needed or changes applied successfully
- 1 - Failure: Changes needed (in
--checkmode) or processing error occurred
Integration
Pre-commit Hook
Add to .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: spacing
name: spacing
entry: spacing
language: system
types: [python]
CI/CD
# Check formatting in CI
spacing --check src/
if [ $? -ne 0 ]; then
echo "Code needs formatting. Run: spacing src/"
exit 1
fi
Editor Integration
Most editors can be configured to run spacing on save or as a format command.
Examples
Before and After
Before:
import os
import sys
def main():
x = 1
y = 2
if x > 0:
print(x)
else:
print(y)
for i in range(10):
process(i)
class Helper:
pass
After (with default config):
import os
import sys
x = 1
y = 2
if x > 0:
print(x)
else:
print(y)
for i in range(10):
process(i)
class Helper:
pass
Comparison with Other Tools
| Feature | Spacing | Black | Ruff |
|---|---|---|---|
| Blank line rules | ✅ Configurable | ✅ Fixed | ✅ Fixed |
| Scope-aware spacing | ✅ Yes | ⚠️ Limited | ⚠️ Limited |
| Indentation handling | ✅ Configurable | ⚠️ Enforces/reformats | ⚠️ Enforces/reformats |
Spacing's Focus: Spacing solves one problem exceptionally well - scope-aware, configurable blank line enforcement. This is a unique capability that Black and Ruff don't provide comprehensively.
Key Differentiators:
- Configurable blank line rules - Control spacing between any block type transition
- Independent scope-level processing - Rules applied within each scope equally
- Works with your indentation - Detects existing style, never reformats it
Philosophy: Spacing is designed to work alongside Black or Ruff, not replace them. Use Black/Ruff for general formatting (line length, quotes, imports) and Spacing for blank line intelligence.
Troubleshooting
Files Not Being Modified
- Check if files already match the rules:
spacing --check file.py - Use verbose mode to see what's happening:
spacing --verbose file.py - Verify your configuration: check
spacing.tomlsyntax
Unexpected Blank Lines
- Review your configuration file (
spacing.toml) - Use
--dry-runto preview changes:spacing --dry-run file.py - Check for comment blocks that may trigger special rules
- Verify indentation consistency (tabs vs spaces)
Configuration Not Being Applied
- Ensure
spacing.tomlis in the current directory or specify with--config - Check TOML syntax is valid
- Verify values are in valid range (0-3)
- Check block type names match documentation
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for any new functionality
- Ensure all tests pass:
pytest - Run code quality checks:
ruff checkandruff format - Submit a pull request
License
See the LICENSE file for details.
Acknowledgments
Spacing was inspired by the philosophy of tools like Black and Ruff - that automated formatting allows developers to focus on logic rather than style.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spacing-0.7.0.tar.gz.
File metadata
- Download URL: spacing-0.7.0.tar.gz
- Upload date:
- Size: 66.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
260b867ecfd853226d7511ad45a5753b430e385e07b617aedb37016e573c23b8
|
|
| MD5 |
bb10172be58700185c1c2adfc8313356
|
|
| BLAKE2b-256 |
dfb3500441e86a371e338887e3407b0d73528d8b6802671b37a4a817537c468f
|
File details
Details for the file spacing-0.7.0-py3-none-any.whl.
File metadata
- Download URL: spacing-0.7.0-py3-none-any.whl
- Upload date:
- Size: 36.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
882b87cffbc3febf22aa25003914d16a5b1b5d4576047937c2469a63ac21a8aa
|
|
| MD5 |
2ab4b7cc770982918146026497991fd1
|
|
| BLAKE2b-256 |
c812a75ca3cb811042271d427bf6d4b9a24fa9f9283ca558cc4b2483c2bb9c1a
|