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/elsecompletion clause into straight-line code when everyexceptbody 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-autofixablemakes 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e883ddd842e5e527de3fb1c87c07e63cf406fdc0ab21b4471fc2d383918f5ca8
|
|
| MD5 |
ad9d7d58db0b16771e35521d2eeeffb7
|
|
| BLAKE2b-256 |
88e5eb1aaf2a69a1c0138df2f1023212bf1bd07fe80b0b19ef0fb2da4b8bc6f7
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4951e9d57f7fea227c99b3eb9ad978a3e49a84d5547cc346855ef13494251047
|
|
| MD5 |
5020504a03cbdda769d74845bf2f9dc2
|
|
| BLAKE2b-256 |
84145b24263f9d2e07cdef65ba8e9d41f0729d3d2ff3034905eb19ef67d797d3
|