True — The Uncompromising Python Formatter
True is an opinionated Python source-code formatter. It enforces a consistent style so you never have to think about formatting again.
Table of Contents
- Features
- Installation
- Quick Start
- CLI Reference
- API Reference
- PEP 8 Rules Applied
- Architecture
- Contributing
Features
| Category | What True fixes |
|---|---|
| Whitespace | Spaces around operators, after commas, inside brackets |
| Indentation | Any indent style → 4 spaces |
| Blank lines | 2 before top-level def/class, 1 between methods, 2 after last def |
| Imports | Split import os, sys into separate lines |
| Strings | Normalise 'hello' → "hello" |
| Semicolons | x = 0; y = 0 → two lines |
| Compound statements | if x: pass → split onto two lines |
| Lambda assignment | f = lambda x: x → def f(x): return x |
| Class names | class my_class → class MyClass |
| Variable names | xValue → x_value, calcArea → calc_area |
| Default params | def f(x =1) → def f(x=1) |
| Annotated params | def f(x: int=1) → def f(x: int = 1) |
| Keyword args | foo(key =1) → foo(key=1) |
| Power operator | 2 ** 10 → 2**10 |
| Extra spaces | for i in range → for i in range |
| Trailing whitespace | Stripped from every line |
| Final newline | Exactly one trailing newline |
Zero dependencies — pure stdlib.
Installation
pip install true-formatter
Or from source:
git clone https://github.com/yourname/true-formatter
cd true-formatter
pip install -e .[dev]
Quick Start
CLI
# Format a file in-place
true my_script.py
# Check without modifying (for CI)
true --check my_script.py
# Show a unified diff
true --diff my_script.py
# Format an entire directory
true src/
# Read from stdin
echo "x=1" | true -
Library
import true_formatter
code = "x=1\ny= 'hello'\n"
result = true_formatter.format_str(code, mode=true_formatter.Mode())
print(result)
# x = 1
# y = "hello"
CLI Reference
Synopsis
true [OPTIONS] [FILES|DIRECTORIES]...
Pass - as a file path to read from stdin and write to stdout.
Options
| Flag | Default | Description |
|---|---|---|
--check |
off | Don't write files; exit 1 if any would change |
--diff |
off | Print a unified diff instead of rewriting |
-l, --line-length INT |
88 |
Maximum line length |
-S, --skip-string-normalization |
off | Leave string quotes as-is |
-q, --quiet |
off | Suppress all non-error output |
Exit Codes
| Code | Meaning |
|---|---|
0 |
All files already formatted (or successfully reformatted) |
1 |
--check mode: at least one file would be reformatted |
Examples
# Format a single file
true app.py
# Format all Python files in a directory
true src/
# CI — fail if anything needs reformatting
true --check src/ tests/
# Preview changes before applying
true --diff app.py
# Pipe from stdin
cat messy.py | true - > clean.py
# Use a shorter line length
true -l 79 legacy_module.py
# Preserve existing string quotes
true -S my_file.py
Integration
pre-commit:
repos:
- repo: local
hooks:
- id: true-formatter
name: true
entry: true
language: python
types: [python]
Makefile:
fmt:
true src/ tests/
check:
true --check src/ tests/
GitHub Actions:
- name: Check formatting
run: |
pip install true-formatter
true --check src/ tests/
API Reference
Top-level Functions
format_str(src_contents, *, mode) → str
Format a Python source string and return the result.
import true_formatter
result = true_formatter.format_str("x=1", mode=true_formatter.Mode())
# → "x = 1\n"
| Parameter | Type | Description |
|---|---|---|
src_contents |
str |
Python source code to format |
mode |
Mode |
Formatting configuration |
Raises TypeError if src_contents is not a str.
Raises TrueFormattingError if the source cannot be tokenized.
format_file_contents(src_contents, *, mode) → str
Like format_str but additionally guarantees a single trailing newline.
Use this when writing back to a file.
formatted = true_formatter.format_file_contents(src, mode=mode)
Path("my_file.py").write_text(formatted, encoding="utf-8")
check_format(src_contents, *, mode) → bool
Return True if src_contents is already correctly formatted. No files are written.
ok = true_formatter.check_format("x = 1\n", mode=true_formatter.Mode())
# → True
ok = true_formatter.check_format("x=1\n", mode=true_formatter.Mode())
# → False
Mode
Dataclass that holds all formatting options.
from true_formatter import Mode
mode = Mode(
line_length=79,
string_normalization=True,
skip_string_normalization=False,
magic_trailing_comma=True,
)
| Field | Type | Default | Description |
|---|---|---|---|
line_length |
int |
88 |
Maximum line length |
string_normalization |
bool |
True |
Rewrite '…' as "…" |
skip_string_normalization |
bool |
False |
Disable string normalisation |
magic_trailing_comma |
bool |
True |
Preserve trailing commas |
target_versions |
set[TargetVersion] |
set() |
Python versions to target |
rules |
RuleSet |
DEFAULT_RULES |
Active rule set |
Properties:
mode.quote_char— returns'"'or"'"based on normalisation settings.
TargetVersion
Enum of supported Python versions.
from true_formatter import TargetVersion, Mode
mode = Mode(target_versions={TargetVersion.PY311, TargetVersion.PY312})
Values: PY38, PY39, PY310, PY311, PY312
Rules
Rule (abstract base class)
Extend this to create custom formatting rules that operate on the token stream.
import tokenize
from true_formatter import Rule, Mode
class BanPassRule(Rule):
name = "ban-pass"
def apply(self, tokens, mode):
return [t for t in tokens if t.string != "pass"]
| Attribute | Type | Description |
|---|---|---|
name |
str |
Unique identifier shown in --diff output |
Required method: apply(tokens, mode) → list[TokenInfo]
RuleSet
An ordered, immutable-style collection of rules.
from true_formatter import DEFAULT_RULES, Mode
from my_rules import BanPassRule
custom_rules = DEFAULT_RULES.add(BanPassRule())
mode = Mode(rules=custom_rules)
| Method | Returns | Description |
|---|---|---|
add(rule) |
RuleSet |
New set with rule appended |
remove(name) |
RuleSet |
New set without the named rule |
__len__() |
int |
Number of rules |
__iter__() |
iterator | Iterate over rules in order |
Exceptions
TrueError
├── TrueFormattingError ← source cannot be parsed / formatted
└── TrueConfigError ← invalid Mode configuration
from true_formatter.exceptions import TrueFormattingError
try:
true_formatter.format_str("def (:", mode=true_formatter.Mode())
except TrueFormattingError as e:
print(f"Bad source: {e}")
PEP 8 Rules Applied
| Code | Rule | Example |
|---|---|---|
| E101 | Indentation → 4 spaces | tab/2-space → 4 space |
| E201 | No space after ( [ { |
foo( x ) → foo(x) |
| E202 | No space before ) ] } |
foo(x ) → foo(x) |
| E203 | No space before , ; : |
x , y → x, y |
| E211 | No space before ( in call |
foo (x) → foo(x) |
| E225 | Spaces around operators | x==1 → x == 1 |
| E231 | Space after , : ; |
[1,2] → [1, 2] |
| E251 | No spaces around = in default params |
def f(x =1) → def f(x=1) |
| E252 | Spaces around = in annotated params |
def f(x: int=1) → def f(x: int = 1) |
| E261 | Two spaces before inline comment | x=1 # note → x = 1 # note |
| E262 | Space after # in comment |
#note → # note |
| E271 | Single space after keyword | for i → for i |
| E302 | Two blank lines before top-level def/class | |
| E301 | One blank line between methods in class | |
| E305 | Two blank lines after last def before code | |
| E401 | One import per line | import os, sys → two lines |
| E702 | No semicolons between statements | x=0; y=0 → two lines |
| E711 | Compound statements split onto separate lines | if x: pass → two lines |
| E731 | Lambda assignment → def | f = lambda x: x → def f(x): return x |
| W291 | No trailing whitespace | |
| W292 | File ends with newline | |
| N801 | Class names in PascalCase | class my_class → class MyClass |
| N802 | Function/variable names in snake_case | calcArea → calc_area |
Architecture
Formatting Pipeline
Source string
│
▼
Tokenizer (stdlib tokenize)
│
▼
Rule pipeline (true_formatter.rules)
│ each Rule: token list → token list
▼
Emitter (tokenize.untokenize)
│
▼
Text-level passes (true_formatter.transforms)
│
▼
Formatted string
Text-level Passes (in order)
| Pass | PEP 8 codes |
|---|---|
fix_indentation |
E101, W191 |
fix_multiple_imports |
E401 |
split_semicolons |
E702, E711 |
fix_lambda_assignment |
E731 |
fix_extra_whitespace |
E271, E272 |
fix_class_names |
N801 |
fix_variable_names |
N802 |
fix_pep8_whitespace |
E201, E202, E203, E211, E225, E231, E251, E252, E261, E262 |
fix_operator_priority_spacing |
E226 |
normalise_strings |
W605 |
fix_trailing_whitespace |
W291, W293 |
fix_blank_lines |
E302, E301 |
fix_blank_lines_after_def |
E305 |
ensure_final_newline |
W292 |
Module Map
true_formatter/
├── __init__.py Public API — re-exports from submodules
├── core.py format_str / format_file_contents / check_format
├── transforms.py Text-level formatting passes
├── rules.py Rule base class, RuleSet, built-in rules
├── exceptions.py TrueError hierarchy
└── cli.py argparse CLI entry point
Contributing
Setup
git clone https://github.com/yourname/true-formatter
cd true-formatter
pip install -e .[dev]
Running the Tests
pytest # all tests
pytest tests/test_transforms.py # one module
pytest -k "test_single" # filter by name
pytest --tb=short # compact tracebacks
Writing a New Rule
Rules live in true_formatter/rules.py and operate on the token stream:
import tokenize
from true_formatter.rules import Rule
class NoSemicolonRule(Rule):
name = "no-semicolons"
def apply(self, tokens, mode):
return [t for t in tokens if t.string != ";"]
Add it to DEFAULT_RULES and write tests in tests/test_rules.py.
Writing a New Text-level Transform
Transforms live in true_formatter/transforms.py and operate on strings:
def remove_semicolons(src: str) -> str:
return src.replace(";", "")
Wire it into _text_level_format in core.py in the correct position in the pipeline.
Test Guidelines
- One test class per module under test
- Each test method tests exactly one behaviour
- Use
pytest.raisesfor exception assertions - Use
tmp_pathfixture for file I/O tests - Name tests
test_<thing_being_tested>
Code Style
True is formatted with itself:
true true_formatter/ tests/
License
MIT © True Contributors
Metadata
Release files for true-formatter 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| true_formatter-0.2.0.tar.gz | 26.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| true_formatter-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.8 kB
Release files / true_formatter-0.2.0.tar.gz
| Download URL | true_formatter-0.2.0.tar.gz |
|---|---|
| Size | 26.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e5176cc5d6ee8717ba570fd099670be2600138b9ef054bdef98df420e72fdffc
|
|
BLAKE2b-256 checksum How to use checksums |
138ac78701a65173d819e837319402caa2dca67027c4cd44c1ff1f5f74d9bd78
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / true_formatter-0.2.0-py3-none-any.whl
| Download URL | true_formatter-0.2.0-py3-none-any.whl |
|---|---|
| Size | 18.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
17e906e0161f937a70b52e15c519d126245a4222464969426ae56cb3f5cdb93c
|
|
BLAKE2b-256 checksum How to use checksums |
b4927a87021f46a8dc9c2ceb2d908fca16b3999d514ed9da4c44d80ed3cd3b08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|