Skip to main content

Well, actually — an opinionated Python linter and formatter: bans else and elif, allows only flat ternaries, and enforces blank lines around return

Project description

actually

Well, actually, your code should read like this.

actually is a highly opinionated Python linter and formatter built on ast-grep. It enforces a guard-clause style through rules with ruff-style stable codes, grouped by language construct (ADR 1). Every rule is checkable; the auto-fix column marks what format can rewrite. Each rule links to its documentation page with rationale and a banned/wanted example pair — generated from rules.toml and validated by the linter itself (ADR 2). actually rules --list prints the same catalog in the terminal, docs links included:

actually-conditionals

Code Rule Status Auto-fix What it enforces
ACTC001 no-else stable partial else on if, and the completion clauses on for, while, try
ACTC002 no-elif stable no elif — use guard clauses or a dispatch table
ACTC003 ternary-not-nested stable no a ternary inside another ternary's arm (elif in expression form)
ACTC004 ternary-not-empty unstable no a degenerate ternary arm (None, "", empty container) — conditional inclusion in disguise

actually-literals

Code Rule Status Auto-fix What it enforces
ACTL001 trailing-comma unstable yes a dict/list/set literal whose last element lacks a trailing comma
ACTL002 one-element-per-line unstable partial a dict/list/set literal with elements sharing a line with a bracket or each other

actually-returns

Code Rule Status Auto-fix What it enforces
ACTR001 blank-before-return stable yes a return stacked directly under other statements in its block
ACTR002 blank-after-return stable yes code directly under a return line

Usage

uvx well-actually@latest check .
uvx well-actually@latest format .

The @latest matters: a bare uvx well-actually reuses a cached tool environment and can silently run an outdated version; @latest re-resolves against the index every time.

Installed (uv tool install well-actually), the short command is available too:

actually check .
actually format .

check reports violations and exits non-zero when it finds any. Both commands lint .py files only; directory scans skip environment, cache, and VCS directories (.venv, venv, .git, __pycache__, node_modules, and friends) and respect .gitignore files — nested ones and negations included, matched via pathspec (black's approach), so no git installation is required. When a .git directory is found above the scanned path, .gitignore files up to that repo root apply as well. Global excludes (core.excludesFile, .git/info/exclude) are not consulted. A .py file passed explicitly is always linted.

format rewrites files in place, then reports what it could not fix:

  • inserts the missing blank lines around return
  • dedents a try/except/else completion clause into straight-line code when every except body already exits (return, raise, continue, break) — when one falls through, the rewrite would change behaviour, so it is reported for human refactoring instead
  • rewrites dict/list/set literals to one element per line with a trailing comma — literals carrying comments or multiline elements are reported for human formatting instead
  • --only-autofixable makes it best effort: every available fix is applied, the remaining violations are still reported, and the exit code stays 0

Configuration

Select the rule subset in a well-actually.toml (sourced from the current working directory only — never a parent) or with repeatable --include / --exclude options, which override the file's corresponding list (ADR 5). Every invocation declares its selection on stderr — Found well-actually.toml. Running with: … or No well-actually.toml found, running with default '__ALL__' — so the active subset is never a matter of guessing. Entries are rule codes (ACTC004), group prefixes (ACTC), or __ALL__ — the special all-encompassing group (ADR 6). The longest match per rule wins; ties go to exclude. include defaults to __ALL__, so exclude-only configs just work:

exclude = ["ACTL"]

Any subset is expressible — one rule only:

exclude = ["__ALL__"]
include = ["ACTC004"]

or a group off with one member kept:

exclude = ["ACTL"]
include = ["__ALL__", "ACTL001"]

Hard errors instead of silent tolerance: an unknown selector, __ALL__ appearing more than once across both lists, exclude = ["__ALL__"] without any include entry, and a selection that enables no rules. format obeys the selection — a disabled rule neither reports nor fixes.

Example

def describe_config(path):
    try:
        config = parse_json_file(path)
    except ParseError:
        return "invalid config"
    else:
        return describe(config)

actually format rewrites this to:

def describe_config(path):
    try:
        config = parse_json_file(path)
    except ParseError:
        return "invalid config"

    return describe(config)

Development

uv sync
mise install
hk install
uv run pytest

README.md and rules/*.md are generated from README.template.md and src/actually/rules.toml by scripts/generate_docs.py; an hk pre-commit hook regenerates and stages them. Edit the sources, never the outputs.

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

well_actually-0.5.0.tar.gz (17.3 kB view details)

Uploaded Source

Built Distribution

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

well_actually-0.5.0-py3-none-any.whl (22.7 kB view details)

Uploaded Python 3

File details

Details for the file well_actually-0.5.0.tar.gz.

File metadata

  • Download URL: well_actually-0.5.0.tar.gz
  • Upload date:
  • Size: 17.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for well_actually-0.5.0.tar.gz
Algorithm Hash digest
SHA256 e883ddd842e5e527de3fb1c87c07e63cf406fdc0ab21b4471fc2d383918f5ca8
MD5 ad9d7d58db0b16771e35521d2eeeffb7
BLAKE2b-256 88e5eb1aaf2a69a1c0138df2f1023212bf1bd07fe80b0b19ef0fb2da4b8bc6f7

See more details on using hashes here.

File details

Details for the file well_actually-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: well_actually-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 22.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for well_actually-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4951e9d57f7fea227c99b3eb9ad978a3e49a84d5547cc346855ef13494251047
MD5 5020504a03cbdda769d74845bf2f9dc2
BLAKE2b-256 84145b24263f9d2e07cdef65ba8e9d41f0729d3d2ff3034905eb19ef67d797d3

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