Format only the literal string arguments of textwrap.dedent() calls
Project description
format-dedent
Format multiline strings with proper indentation โ A Python code formatter that formats only the literal string arguments of textwrap.dedent() calls.
โจ What does it do?
format-dedent automatically formats multiline strings inside textwrap.dedent() calls to make them visually match their runtime output. This makes your code more readable while preserving the exact behavior.
Key features:
- ๐ฏ Surgical precision โ Only formats strings inside
dedent()calls, leaves everything else untouched - ๐ Two modes โ Format existing dedent strings OR automatically add
dedent()to strings that need it - ๐ Safe โ Validates that formatting doesn't change runtime behavior
- ๐จ Smart indentation โ Aligns content with the visual structure of your code
- ๐งน Clean โ Removes trailing whitespace and normalizes spacing
๐ฆ Installation
pip install format-dedent
๐ Quick Start
Format strings (default mode)
Preview formatted output without modifying files:
python -m format_dedent yourfile.py
Write changes to files:
python -m format_dedent yourfile.py --write
Format multiple files or directories:
python -m format_dedent src/ tests/ --write
Add dedent() calls (--add-dedent mode)
Automatically wrap multiline strings with dedent() calls:
python -m format_dedent yourfile.py --add-dedent --write
This will:
- Find multiline strings where
dedent(str) == str(no leading indentation to remove) - Wrap them with
dedent()calls - Add
from textwrap import dedentimport if needed
๐ Usage Options
python -m format_dedent [OPTIONS] [FILES/DIRECTORIES]
Options:
-w, --write Write changes to files (default: output to stdout)
--add-dedent Add dedent() calls to multiline strings
-h, --help Show help message
Behavior:
- Default โ Output formatted code to stdout (no file modification)
--writeโ Modify files directly and print confirmation
Examples:
# Preview formatted code (output to stdout, no changes)
python -m format_dedent myfile.py
# Write changes to the file
python -m format_dedent myfile.py --write
# Format entire project
python -m format_dedent src/ tests/ --write
# Add dedent() to all multiline strings
python -m format_dedent myfile.py --add-dedent --write
# Read from stdin, write to stdout (pipe-friendly)
cat myfile.py | python -m format_dedent > formatted.py
๐ง Pre-commit Hook
Use format-dedent as a pre-commit hook to automatically format dedent strings before each commit.
Add this to your .pre-commit-config.yaml:
repos:
- repo: https://github.com/15r10nk/format-dedent
rev: v0.1.0 # Use the latest version
hooks:
- id: format-dedent
Then install the hook:
pre-commit install
Run manually on all files:
pre-commit run format-dedent --all-files
๐ก Examples
Example 1: Formatting SQL queries
Before formatting:
import textwrap
def get_sql_query():
return textwrap.dedent("""
SELECT users.name, orders.total
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.status = 'complete'
""")
Inconsistent indentation inside the string makes it hard to read and understand the actual SQL query structure.
After formatting:
import textwrap
def get_sql_query():
return textwrap.dedent("""
SELECT users.name, orders.total
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.status = 'complete'
""")
โ What changed:
- Inconsistent indentation is normalized
- Each line's indentation now reflects the SQL structure
- Trailing whitespace removed
- The formatted version shows what
dedent()will produce at runtime
The key insight: The indentation you see in the source code now matches what dedent() returns. When this code runs, dedent() strips the common leading whitespace, and you get properly formatted SQL.
Example 2: Using --add-dedent mode
Before:
def get_message():
message = """
Hello World!
This is a message.
"""
return message
After running with --add-dedent:
from textwrap import dedent
def get_message():
message = dedent("""
Hello World!
This is a message.
""")
return message
โ What changed:
- Detected that the string has no leading whitespace (left-aligned)
- Wrapped it with
dedent()for consistency - Added the import statement automatically
- Reformatted with proper indentation matching the code structure
Why use dedent here? Even though this string doesn't need dedenting now, using dedent() consistently makes it easier to modify the string later. You can add indentation for readability without affecting the runtime output.
Example 3: HTML template formatting
Before:
from textwrap import dedent
def render_html():
return dedent("""
<div class="container">
<h1>Welcome</h1>
<p>This is a paragraph.</p>
</div>
""")
After:
from textwrap import dedent
def render_html():
return dedent("""
<div class="container">
<h1>Welcome</h1>
<p>This is a paragraph.</p>
</div>
""")
Example 4: JSON template
Before:
import textwrap
CONFIG = textwrap.dedent('''
{
"name": "my-app",
"version": "1.0.0"
}
''')
After:
import textwrap
CONFIG = textwrap.dedent('''
{
"name": "my-app",
"version": "1.0.0"
}
''')
Example 5: Error message with inconsistent indentation
Before:
from textwrap import dedent
def validate_user(user):
if not user.email:
raise ValueError(dedent("""
Invalid user configuration:
- Email is required
- Must be a valid email address
- Example: user@example.com
"""))
After:
from textwrap import dedent
def validate_user(user):
if not user.email:
raise ValueError(dedent("""
Invalid user configuration:
- Email is required
- Must be a valid email address
- Example: user@example.com
"""))
โ What changed:
- Mixed indentation levels are now consistent
- Each line's indentation shows the message structure
- The formatted version visually matches what users will see at runtime
๐ง How It Works
- Parse โ Uses Python's AST module to analyze source code
- Find โ Locates all
dedent()ortextwrap.dedent()calls with string arguments - Analyze โ Determines the appropriate indentation level based on context
- Format โ Removes trailing whitespace and applies consistent indentation
- Validate โ Ensures
dedent(original) == dedent(formatted)(behavior unchanged) - Replace โ Updates the source file with formatted strings
The key insight: Strings are formatted in the source to visually match their runtime output after dedent() processes them. This makes the code more readable without changing behavior.
๐ก๏ธ Safety & Compatibility
- Non-destructive โ Always validates that
dedent(original) == dedent(formatted) - Preserves behavior โ Formatted strings have identical runtime output
- Quote style aware โ Maintains your choice of
"""vs''' - Escape handling โ Correctly handles backslashes and escape sequences
- Python 3.8+ โ Works with modern Python versions
What gets formatted:
- โ
Literal strings inside
textwrap.dedent()calls - โ
Literal strings inside
dedent()calls (when imported)
What doesn't get formatted:
- โ Regular strings (not in dedent calls)
- โ F-strings (can't be wrapped with dedent)
- โ String concatenations
- โ Docstrings at module level
- โ All other code (completely untouched)
๐งช Development
Setup
# Clone the repository
git clone https://github.com/15r10nk/format-dedent.git
cd format-dedent
# Install with development dependencies
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
Running Tests
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run specific test file
pytest tests/test_formatter.py
Test Suite
The project includes 51 comprehensive tests covering:
- โ Module, function, and class level dedent formatting
- โ Nested blocks (if, for, try/except)
- โ
Quote style preservation (
"""and''') - โ Backslash line continuations
- โ Real-world examples (SQL, HTML, JSON templates)
- โ Edge cases and error handling
- โ CLI integration tests
Tests use inline-snapshot for snapshot testing.
Project Structure
format-dedent/
โโโ src/format_dedent/
โ โโโ __init__.py # Package exports
โ โโโ __main__.py # Entry point
โ โโโ cli.py # CLI interface
โ โโโ formatter.py # String formatting logic
โ โโโ add_dedent.py # Add dedent() calls
โ โโโ ast_helpers.py # AST analysis utilities
โโโ tests/
โโโ test_formatter.py # Formatting tests
โโโ test_cli.py # CLI integration tests
๐ License
MIT License - See LICENSE file for details
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
โญ Related Projects
- Black โ The uncompromising Python code formatter
- inline-snapshot โ Snapshot testing for Python
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 format_dedent-0.1.0.tar.gz.
File metadata
- Download URL: format_dedent-0.1.0.tar.gz
- Upload date:
- Size: 21.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c45b348c6d53943f05a2f3dded7833d8b9309694abacb74790382357121387f2
|
|
| MD5 |
8247d4afd15cb0da0ee9572b8b18999e
|
|
| BLAKE2b-256 |
913a8c7b024c793f5915e00dbec39398908e4c71c3a95dc91dd8a3c2a4de2a5c
|
File details
Details for the file format_dedent-0.1.0-py3-none-any.whl.
File metadata
- Download URL: format_dedent-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb04889eed7918226e0b9f35207dde16a6b786faf3c1b3f3da34fc80618b38aa
|
|
| MD5 |
d4406f815327df7e817f54fa79a467cf
|
|
| BLAKE2b-256 |
2cdd390468f0e95df35ad0de3342feab325be8c84364b2df7c39164c590c243c
|