Skip to main content

The uncompromising Python formatter.

Project description

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: xdef f(x): return x
Class names class my_classclass MyClass
Variable names xValuex_value, calcAreacalc_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 ** 102**10
Extra spaces for i in rangefor 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 , yx, y
E211 No space before ( in call foo (x)foo(x)
E225 Spaces around operators x==1x == 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 # notex = 1 # note
E262 Space after # in comment #note# note
E271 Single space after keyword for ifor 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: xdef f(x): return x
W291 No trailing whitespace
W292 File ends with newline
N801 Class names in PascalCase class my_classclass MyClass
N802 Function/variable names in snake_case calcAreacalc_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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

true_formatter-0.2.0.tar.gz (26.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

true_formatter-0.2.0-py3-none-any.whl (18.1 kB view details)

Uploaded Python 3

File details

Details for the file true_formatter-0.2.0.tar.gz.

File metadata

  • Download URL: true_formatter-0.2.0.tar.gz
  • Upload date:
  • Size: 26.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for true_formatter-0.2.0.tar.gz
Algorithm Hash digest
SHA256 e5176cc5d6ee8717ba570fd099670be2600138b9ef054bdef98df420e72fdffc
MD5 f65e78d3f9f832b9667287a54adb3b93
BLAKE2b-256 138ac78701a65173d819e837319402caa2dca67027c4cd44c1ff1f5f74d9bd78

See more details on using hashes here.

File details

Details for the file true_formatter-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: true_formatter-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 18.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for true_formatter-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 17e906e0161f937a70b52e15c519d126245a4222464969426ae56cb3f5cdb93c
MD5 d9d1872df0a1bdc67804403fbe773f7f
BLAKE2b-256 b4927a87021f46a8dc9c2ceb2d908fca16b3999d514ed9da4c44d80ed3cd3b08

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page