Python Code Formatter

Paste a module, a class or a single function and get Black-style Python back from Ruff, compiled to WebAssembly. If the code does not parse, the error points at the line and column where it went wrong.

Input

Settings

History

Load from URL

Ruff, Black and the style you get

Python has no formatter in the standard library, but Black settled the style argument for most teams: double quotes, spaces around operators, two blank lines between top-level definitions, and long calls split at their brackets instead of with backslashes. Ruff reimplements that style in Rust, and its documentation reports better than 99.9% agreement with Black on projects that are already Black-formatted. This page runs the Ruff formatter compiled to WebAssembly, so the result matches ruff format without installing Python, a virtual environment or a pre-commit hook.

One difference to know about: lines are wrapped at 80 columns here, while Black and Ruff default to 88. A call that sits at 85 characters will be split on this page but left alone by a project that keeps the default line length.

Formatting a file

  1. Paste the code or drop a .py or .pyi file onto the input. A #!/usr/bin/env python3 shebang, or def and class lines ending in colons, are enough for detection; otherwise pick Python from the format menu.
  2. Check Indent. Python defaults to 4 spaces, the width PEP 8 and Black both use and the one most linters expect. Choose 2 spaces or tabs only if a project really uses them.
  3. Hit Ctrl/Cmd+Enter to run Ruff; Ctrl/Cmd+Shift+C puts the output on your clipboard.

The parser and printer both run inside the page, which matters when the snippet you are cleaning up still has a database URL or an API token in it.

Quotes and magic trailing commas

  • Quotes chooses how string literals are written. Double is the Black default and turns 'pending' into "pending". Single does the reverse. Either way a string keeps its original quotes if switching would mean adding backslash escapes. As written leaves every quote alone. Docstrings and other triple-quoted strings always come out with double quotes, as PEP 257 recommends, even when Single is selected.
  • Magic trailing comma decides what a comma after the last element means. With Respect, a trailing comma in a call, list or dict forces one item per line, which is how you keep a short config dict expanded on purpose. With Ignore, the formatter collapses the collection onto one line when it fits and drops the comma.

What the formatter will and will not touch

Formatting changes layout only: whitespace, blank lines, quote characters, optional parentheses, line breaks and trailing commas. It does not sort imports (import os,sys becomes import os, sys, still on one line), remove unused names, rename variables or modernise syntax. For import sorting and lint fixes, use ruff check --fix in your project.

Comments stay where they are. Lines between # fmt: off and # fmt: on are passed through untouched, and a statement ending in # fmt: skip keeps its exact spacing, which is useful for hand-aligned matrices and lookup tables.

The parser understands current Python 3 syntax, including match statements, the PEP 695 type alias and generic def first[T](...) forms, walrus assignments and nested quotes inside f-strings. Python 2 code is a different story: a bare print "hello" statement is a syntax error, not something the formatter can rewrite.

Examples

Flask route squeezed onto few lines

With 4-space indentation the one-line if is split, quotes become double, and the long return is wrapped at its brackets.

Input
from flask import Flask,jsonify
app=Flask(__name__)
@app.route('/orders/<order_id>',methods=['GET'])
def get_order(order_id:str):
    order=db.get(order_id)
    if order is None: return jsonify({'error':'not found'}),404
    return jsonify({'id':order.id,'total':order.total,'items':[i.to_dict() for i in order.items]})
Output
from flask import Flask, jsonify

app = Flask(__name__)


@app.route("/orders/<order_id>", methods=["GET"])
def get_order(order_id: str):
    order = db.get(order_id)
    if order is None:
        return jsonify({"error": "not found"}), 404
    return jsonify(
        {
            "id": order.id,
            "total": order.total,
            "items": [i.to_dict() for i in order.items],
        }
    )
Open this example in the tool

Collapsing collections with Ignore and Single quotes

Both trailing commas are dropped so each statement fits on one line, and the strings switch to single quotes.

Input
CONFIG = {
    "region": "ap-southeast-1",
    "retries": 3,
}
users = fetch_users(
    active=True,
    limit=50,
)
Output
CONFIG = {'region': 'ap-southeast-1', 'retries': 3}
users = fetch_users(active=True, limit=50)
Open this example in the tool

Structural pattern matching on webhook events

Case bodies move onto their own lines and the guard clause gets spaces around its comparison.

Input
match event:
  case {'type':'order.created','id':oid}: create_invoice(oid)
  case {'type':'order.refunded','id':oid,'amount':amt} if amt>0: refund(oid,amt)
  case _: log.warning('unhandled event %s',event.get('type'))
Output
match event:
    case {"type": "order.created", "id": oid}:
        create_invoice(oid)
    case {"type": "order.refunded", "id": oid, "amount": amt} if amt > 0:
        refund(oid, amt)
    case _:
        log.warning("unhandled event %s", event.get("type"))
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
Expected a parameter or the end of the parameter list at byte range 6..7Something that is not a parameter name sits inside a def signature, such as the stray colon in def f(:.Look at the column shown and remove the extra character, or add the missing parameter name.
Unexpected indentation at byte range 14..22A line is indented deeper than the block it belongs to, often because tabs and spaces were mixed or a paste added leading spaces.Re-indent the block consistently with spaces, then format again.
Expected `:`, found newline at byte range 4..5An if, for, while, def or class header is missing its trailing colon.Add the colon at the end of the header line.
Expected an indented block after function definition at byte range 9..15The function body starts at column 1. Chat apps and some web pages strip leading spaces when code is copied.Indent the body under the def line, or copy the code again from a source that keeps whitespace.
Simple statements must be separated by newlines or semicolons at byte range 6..13Usually Python 2 code: print “hello” without parentheses is two expressions side by side in Python 3.Convert the statement to print(“hello”), or run the file through a 2-to-3 migration tool first.
unexpected EOF while parsing at byte range 13..13A bracket or parenthesis opened earlier is never closed, so the parser ran off the end of the input.Find the call or literal that is missing its closing ) ] or } and add it.

Frequently asked questions

Is the output the same as Black?

Almost always. Ruff follows the Black style closely, and the differences that remain are rare edge cases. The bigger practical difference is the 80-column wrap here versus 88 in a default Black or Ruff setup.

Can I format Python with 2-space or tab indentation?

Yes. Indentation defaults to 4 spaces for PEP 8, but the Indent menu in the toolbar also offers 2 spaces and tabs, and your browser keeps the choice for later visits.

Does it sort imports or remove unused ones?

No. Like ruff format and Black, it only changes layout. Import sorting is a lint rule in Ruff (rule set I) or the job of isort.

How do I stop the formatter from changing one block?

Wrap the block in # fmt: off and # fmt: on comments, or end a single statement with # fmt: skip. The text in between is copied through exactly.

Which Python versions are supported?

Python 3 syntax up to recent releases, including match statements, type aliases, generic functions and modern f-strings. Python 2 only constructs such as print statements fail to parse.

Related tools