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

Standing on Ruff and Ty

actually deliberately covers only what ruff and ty cannot express — they do most of the lifting; adopt them first. Our recommended configurations are this repo's own ruff.toml and ty.toml. actually's opinionation starts where those stop, and it MANDATES compatibility of its own output: no actually rule demands, and no actually format fix produces, code those rule sets reject — after actually format, a ruff format under the recommended configuration is a no-op. well-actually never runs ruff or ty itself — it is its own tool with neither as a dependency; pair them in your own pipeline. This repo gates itself on both toolchains plus its own linter on every commit, which is the guarantee exercised live.

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.

CI Reports

check and format emit machine-readable reports via --output-format (text/gitlab/github/sarif) and --output-file (ADR 7). GitLab code quality:

actually:
  script:
    - uvx well-actually@latest check --output-format=gitlab --output-file=gl-code-quality-report.json .
  artifacts:
    when: always
    reports:
      codequality: gl-code-quality-report.json

GitHub inline annotations need no upload — --output-format=github prints workflow commands; --output-format=sarif produces SARIF 2.1.0 for GitHub code scanning or any SARIF consumer.

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.6.0.tar.gz (19.4 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.6.0-py3-none-any.whl (25.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: well_actually-0.6.0.tar.gz
  • Upload date:
  • Size: 19.4 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.6.0.tar.gz
Algorithm Hash digest
SHA256 bf611cbe7b65cbcea7f4ffe16eb88eef703db3b3ad469f91a0421104bfd779c9
MD5 a34f602a8680a2438990d745aee3913a
BLAKE2b-256 eb18adc45a3b518b6ea5d16ae20571faa88cc3867d69c162605b6bf0a1df1120

See more details on using hashes here.

File details

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

File metadata

  • Download URL: well_actually-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 25.0 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.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b37f06dfe97dbd58acf26297659f940733b1a02d9076a4754071218867c3c42b
MD5 2808044390646c5dfb81afa434fd51ce
BLAKE2b-256 6daac0e8e51b81814172b4ba1ee2d12b79f0c10f298e178a36a6502ba5057da0

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