Skip to main content

Style checker for fast.ai coding conventions

Project description

fastaistyle

pip install fastaistyle

A style checker that enforces the fast.ai coding style—a compact, readable approach to Python that keeps more code visible on screen and reduces cognitive load.

Why This Style?

Most style guides optimize for the wrong thing. They add vertical space, mandate verbose names, and scatter related code across many lines. The result? You see less code at once, which means more scrolling, more context-switching, and more mental effort to understand what's happening.

The fast.ai style takes a different approach, rooted in decades of experience with APL, J, K, and scientific programming. The core insight comes from Kenneth Iverson: "brevity facilitates reasoning."

Your Brain Can Only Hold So Much

When you're reading code, your working memory is limited. If a function spans 50 lines, you can't see the whole thing at once. You scroll down, forget what was at the top, scroll back up. Each scroll is a context switch. Each context switch costs mental energy.

But if that same function fits in 15 lines? You see the whole picture. Your eyes can jump between related parts instantly. Patterns become obvious. Bugs stand out.

This isn't about cramming code together—it's about removing unnecessary vertical space so your brain can do what it's good at: recognizing patterns across visible information.

One Line, One Idea

The goal is density without confusion. Each line should express one complete thought:

# Good: you see the whole pattern at once
if not data: return None
for item in items: process(item)
def _is_ready(self): return self._ready.is_set()

# Bad: same logic, but now it's 6 lines instead of 3
if not data:
    return None
for item in items:
    process(item)
def _is_ready(self):
    return self._ready.is_set()

When the body is simple, keep it on the same line. Save vertical space for code that actually needs it.

Names Should Be Short (When Used Often)

This follows "Huffman coding" for variable names—frequently used things get short names:

# Good: conventional, recognizable
img, i, msg, ctx

# Bad: verbose for no benefit
image_data, loop_index, message_object, context_instance

Domain experts recognize nll (negative log likelihood) instantly. Spelling it out doesn't help them, and the extra characters push code off the right edge of the screen.

Installation

pip install fastaistyle

Or install from source:

git clone https://github.com/AnswerDotAI/fastaistyle
cd fastaistyle
pip install -e .

Usage

Check the current directory:

chkstyle

Check a specific path:

chkstyle path/to/code/

Check multiple files/folders in one run:

chkstyle path/to/code tests unit.py

Fix selected mechanical violations in place:

chkstyle --fix path/to/code
chkstyle --fix --fix-rule dict-literal --fix-rule single-statement-body path/to/code

Skip paths matching a regex. This uses Python re.match, so add ^ / $ yourself when you want exact matches:

chkstyle --skip-path-re 'test|migrations|vendor|src/gen'

Skip specific paths by name/path (repeatable):

chkstyle --skip-path vendor --skip-path src/generated

The checker prints violations with file paths, line numbers, the offending code, and a short fix hint. When violations are found, it also prints a reminder to prioritize clarity and match the spirit of the style guide.

Jupyter Notebook Support

chkstyle automatically checks .ipynb files alongside .py files. For notebooks, violations show the cell ID and line number within the cell:

# notebook.ipynb:cell[abc123]:3: lhs assignment annotation
x: int = 1

Configuration

Configure chkstyle in your pyproject.toml:

[tool.chkstyle]
skip_paths = ["vendor", "src/generated"]
skip-path-re = "test|migrations|vendor|src/gen"
ignore = ["line-too-long"]
fix = ["dict-literal", "single-statement-body"]

Command-line skip arguments override config values. --ignore adds rule IDs to configured ignores; --fix-rule selects the fixer rules for that run when provided.

What It Checks

dict literal with 3+ identifier keys

Use dict() for keyword-like keys when all keys can be valid Python keyword arguments—it's easier to scan and produces cleaner diffs.

# Bad
payload = {"host": host, "port": port, "timeout": timeout}

# Good
payload = dict(host=host, port=port, timeout=timeout)

single-statement body not one-liner

If the body is one simple statement, keep it on the header line when the result is short enough and no comments need to move.

# Bad
if ready:
    return True

# Good
if ready: return True

single-line docstring uses triple quotes

Triple quotes are for multi-line strings. Single-line docstrings should use regular quotes.

# Bad
def foo():
    """Return the value."""
    return x

# Good
def foo():
    "Return the value."
    return x

multi-line from-import

If it fits on one line, keep it on one line.

# Bad
from os import (
    path,
    environ,
)

# Good
from os import path, environ

consecutive short imports

If you have a run of short import foo lines, combine them.

# Bad
import os
import sys
import pathlib

# Good
import os, sys, pathlib

unused import

Remove imports that are never referenced.

# Bad
import os

# Good
import os
print(os.getcwd())

Imports named in a simple static __all__ count as used. Package __init__.py files are exempt from this rule so re-export modules stay quiet.

For notebooks, unused import is checked across #| export / #| exports cells as one exported module. If an exported-cell import is only referenced from non-exported cells, chkstyle asks you to move it into a non-exported imports cell, recommending the first non-exported cell that already has imports when it can find one.

closing bracket on its own line

Don't leave a bare closing ), ], or } on a line by itself.

# Bad
items = [
    one,
    two,
]

# Good
items = [
    one,
    two]

continuation line indent

Continuation lines should be indented exactly 4 spaces beyond the line that opened the block.

# Bad
result = call(
        first_arg,
        second_arg)

# Good
result = call(
    first_arg,
    second_arg)

line >160 chars

Wrap at a natural boundary: argument lists, binary operators, or strings. 160 is the hard limit, but aim for ~140 (or ~120 when practical). Long lines are only exempt when the extra width mainly comes from string literal content.

semicolon statement separator

Don't use ; to combine statements. Use separate lines.

inefficient multiline expression

If the content would fit in fewer lines, condense it.

# Bad
result = call(
    a,
    b,
    c,
)

# Good
result = call(a, b, c)

lhs assignment annotation

Avoid x: int = 1 and bare x: int annotations in normal code. Put type hints on function parameters and return values instead. Dataclass and BaseModel fields are exempt, since their annotations are part of the class contract. Auto-fixing only rewrites value-bearing assignments like x: int = 1.

# Bad
x: int = 1
name: str = "hello"

# Good (in a function signature)
def process(x: int, name: str) -> Result: ...

nested generics depth >= 2

Keep parameter annotations simple. Deep nesting makes them hard to read.

# Bad
def process(items: list[dict[str, list[int]]]): ...

# Good
Payload = dict[str, list[int]]
def process(items: list[Payload]): ...

Opting Out

Sometimes you have a good reason to format code a specific way. The checker supports pragmas:

# Ignore a single line
x: int = 1  # chkstyle: ignore

# Ignore the next line
# chkstyle: ignore
y: int = 2

# Disable for a block
# chkstyle: off
carefully_formatted = {
    "alignment": "matters",
    "here":      "for readability",
}
# chkstyle: on

# Skip an entire file (must be in first 5 lines)
# chkstyle: skip

Running Tests

pytest

There are currently no tests marked slow; running pytest -m slow selects 0 tests.

Development

Create a PR with a label (for release notes):

./tools/pr.sh enhancement "Add new feature"
./tools/pr.sh bug "Fix something"

Release after PRs are merged:

./tools/release.sh        # patch bump (default)
./tools/release.sh minor  # minor bump
./tools/release.sh major  # major bump

This tags the current version, pushes to trigger PyPI publish, then bumps for the next dev cycle.

Philosophy

This tool exists to catch mechanical issues, not to enforce taste. The violations it reports are almost always things you'd want to fix—extra vertical space that doesn't help, type annotations that clutter rather than clarify, formatting that makes diffs noisier than necessary.

The goal is code that's pleasant to read and easy to maintain. Dense, but not cramped. Clear, but not verbose. When in doubt, look at the surrounding code and match its style.

For the full style guide, see:

License

Apache 2.0

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

fastaistyle-0.0.16.tar.gz (28.9 kB view details)

Uploaded Source

Built Distribution

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

fastaistyle-0.0.16-py3-none-any.whl (20.4 kB view details)

Uploaded Python 3

File details

Details for the file fastaistyle-0.0.16.tar.gz.

File metadata

  • Download URL: fastaistyle-0.0.16.tar.gz
  • Upload date:
  • Size: 28.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for fastaistyle-0.0.16.tar.gz
Algorithm Hash digest
SHA256 111e2e4c11fb0773c15fcdef1c09aac8160b4922db6d044780047dc35a190485
MD5 5aa369878541c2c0ddc1e411b03ab536
BLAKE2b-256 26f40ba699301c99b86eabf7a52f273544ffcfc10a690f71f85bd6ade280aa82

See more details on using hashes here.

File details

Details for the file fastaistyle-0.0.16-py3-none-any.whl.

File metadata

  • Download URL: fastaistyle-0.0.16-py3-none-any.whl
  • Upload date:
  • Size: 20.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.0

File hashes

Hashes for fastaistyle-0.0.16-py3-none-any.whl
Algorithm Hash digest
SHA256 ff581494ecad60012f1a6640c941d7ca792d4f61ab0ab75838fbedf8126ae465
MD5 bfaa5188acf1c8e668530243e986638a
BLAKE2b-256 fce5fef6907777d25e0cf2257abdd9a983eb17d4a00a1bf3bb43e0b8fe1e1dc8

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