Skip to main content

True — The Uncompromising Python Formatter

Python License: MIT

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

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.raises for exception assertions
  • Use tmp_path fixture 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)

Source distribution for true-formatter 0.2.0
File Size Uploaded
true_formatter-0.2.0.tar.gz 26.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for true-formatter 0.2.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.3

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page