tempest-cli 0.5.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/CHANGELOG.md +52 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/PKG-INFO +6 -5
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/README.md +5 -4
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/changelog.en.md +36 -1
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/changelog.md +34 -1
- tempest_cli-0.6.0/docs/commands.en.md +303 -0
- tempest_cli-0.6.0/docs/commands.md +299 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/installation.en.md +3 -2
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/installation.md +3 -2
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/library.en.md +3 -2
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/library.md +3 -2
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/pyproject.toml +1 -1
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/__init__.py +1 -1
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/lint.py +164 -51
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/main.py +67 -31
- tempest_cli-0.6.0/tempest_cli/residuals.py +356 -0
- tempest_cli-0.6.0/tempest_cli/rewrite.py +417 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/test_extra_steps.py +1 -1
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/test_fast.py +82 -9
- tempest_cli-0.6.0/tests/test_fix_residuals.py +194 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/test_lint_runners.py +4 -3
- tempest_cli-0.6.0/tests/test_rewrite.py +222 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/uv.lock +1 -1
- tempest_cli-0.5.0/docs/commands.en.md +0 -159
- tempest_cli-0.5.0/docs/commands.md +0 -156
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/.github/workflows/ci.yml +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/.github/workflows/docs.yml +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/.github/workflows/release-pypi.yml +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/.gitignore +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/LICENSE +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/Makefile +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/configuration.en.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/configuration.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/index.en.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/index.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/pr-prompt.en.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/pr-prompt.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/reference.en.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/reference.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/rules.en.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/docs/rules.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/mkdocs.yml +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/_templates/pull_request_template.en-US.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/_templates/pull_request_template.pt-BR.md +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/config.py +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/pr_prompt.py +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/py.typed +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tempest_cli/steps.py +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/__init__.py +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/test_cli.py +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/test_config.py +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/test_lint_strictness.py +0 -0
- {tempest_cli-0.5.0 → tempest_cli-0.6.0}/tests/test_pr_prompt.py +0 -0
|
@@ -5,6 +5,58 @@ All notable changes to **tempest-cli** are listed below.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.6.0] — 2026-10-20
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **`test` and `check` run the suite in parallel by default.** The
|
|
15
|
+
pytest-xdist run that needed `--fast` (`-n auto -p no:cacheprovider`) is
|
|
16
|
+
now what a bare `tempest-cli test` does; `--serial` opts out (for
|
|
17
|
+
`--lf` / `--ff`, or a debugger), and `--workers` tunes the default
|
|
18
|
+
without a flag. Measured on a FastAPI service suite of 1136 tests on a
|
|
19
|
+
12-CPU machine: 262 s serially, 37.6 s in parallel, same outcome.
|
|
20
|
+
- **Without pytest-xdist the default degrades instead of failing.** When
|
|
21
|
+
the environment pytest runs in cannot import it, a bare `test` / `check`
|
|
22
|
+
prints a one-line notice naming the install command and runs the suite
|
|
23
|
+
serially (exit code is pytest's); an explicit `--fast` keeps the old
|
|
24
|
+
behavior — the fix is printed and the exit code is 127, nothing runs.
|
|
25
|
+
`run_pytest` / `run_full_check` take `require_xdist=` (default `True`,
|
|
26
|
+
the old library behavior) to pick between the two.
|
|
27
|
+
- `--workers` with `--serial` is a usage error (exit 2), as `--workers`
|
|
28
|
+
without `--fast` used to be.
|
|
29
|
+
|
|
30
|
+
## [0.5.1] — 2026-10-15
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **`fix` explains the E501 the formatter cannot resolve.** A line still
|
|
35
|
+
too long after `ruff format` is one the formatter has nowhere to break
|
|
36
|
+
— a dotted name (it never splits one, and drops parentheses that do not
|
|
37
|
+
make the line fit), a string literal, a comment — but the bare
|
|
38
|
+
`Line too long (94 > 79)` read as if `line-length` were being ignored.
|
|
39
|
+
`fix` now reads ruff's JSON report of the leftovers and names each such
|
|
40
|
+
line with the overflowing token, its kind and the action that does
|
|
41
|
+
resolve it, then splits the residual violations into those with no
|
|
42
|
+
autofix and those `fix --unsafe` would fix. The exit code is unchanged:
|
|
43
|
+
CI still fails on them. Closes #3.
|
|
44
|
+
- **`fix` shortens the long comments and strings the formatter leaves
|
|
45
|
+
alone.** After `ruff format`, each E501 that is a comment is wrapped at
|
|
46
|
+
the limit (a trailing one moves above its line) and each that is a
|
|
47
|
+
plain string literal is split at its spaces into implicitly
|
|
48
|
+
concatenated parts inside parentheses; the touched files are formatted
|
|
49
|
+
again. Every edit is kept only when the file's AST is identical before
|
|
50
|
+
and after, so the program cannot change. Tool pragmas (`noqa`,
|
|
51
|
+
`type: ignore`, `fmt:`, …), f-strings, triple-quoted strings and words
|
|
52
|
+
longer than a line are left alone and explained instead. On by
|
|
53
|
+
default: neither rewrite changes behavior.
|
|
54
|
+
- **`fix` reports on the formatted file.** The passes are now
|
|
55
|
+
`ruff check --fix-only`, `ruff format`, then `ruff check`; before, the
|
|
56
|
+
report and the exit code came from the lint pass that ran *before* the
|
|
57
|
+
formatter, so a violation the formatter went on to resolve still showed
|
|
58
|
+
up and still failed the command.
|
|
59
|
+
|
|
8
60
|
## [0.5.0] — 2026-10-13
|
|
9
61
|
|
|
10
62
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tempest-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Framework-agnostic quality gate for Python projects: ruff + mypy + pytest behind one command, with a typing-strictness dial and a PR-description prompt generator.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mauriciobenjamin700/tempest-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/mauriciobenjamin700/tempest-cli
|
|
@@ -70,12 +70,12 @@ remembered to pass.
|
|
|
70
70
|
| Command | Runs |
|
|
71
71
|
| --- | --- |
|
|
72
72
|
| `tempest-cli lint` | `ruff check` |
|
|
73
|
-
| `tempest-cli fix` | `ruff check --fix` then `ruff
|
|
73
|
+
| `tempest-cli fix` | `ruff check --fix-only`, `ruff format`, wraps long comments and splits long strings the formatter leaves alone, then a final `ruff check` that explains what is left (`--unsafe` for the risky autofixes) |
|
|
74
74
|
| `tempest-cli format` | `ruff format` (writes) |
|
|
75
75
|
| `tempest-cli fmt-check` | `ruff format --check` (read-only) |
|
|
76
76
|
| `tempest-cli type` | `mypy` |
|
|
77
|
-
| `tempest-cli test` | `pytest
|
|
78
|
-
| `tempest-cli check` | all four, in order, stopping at the first failure (
|
|
77
|
+
| `tempest-cli test` | `pytest`, in parallel by default with pytest-xdist (`-n auto -p no:cacheprovider`, `-w N` to choose; serial with a notice when it is not installed; `--serial` opts out) |
|
|
78
|
+
| `tempest-cli check` | all four, in order, stopping at the first failure (the test step in parallel as in `test`; a host CLI can add steps between `mypy` and `pytest`) |
|
|
79
79
|
| `tempest-cli pr-prompt` | builds the prompt that makes an AI write this branch's PR description |
|
|
80
80
|
|
|
81
81
|
Every command takes an optional path (`tempest-cli lint src/`) and
|
|
@@ -188,7 +188,8 @@ your suite. Add them yourself, or take the bundle:
|
|
|
188
188
|
uv add --dev "tempest-cli[tools]" # mypy + pytest + pytest-xdist
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
`pytest-xdist` is what `test
|
|
191
|
+
`pytest-xdist` is what `test` runs the suite in parallel with, by
|
|
192
|
+
default (without it the suite runs serially, with a notice); its
|
|
192
193
|
`requires-dist` (`execnet>=2.1`, `pytest>=7.0.0`) carries no upper bound.
|
|
193
194
|
|
|
194
195
|
Whatever you pin wins over the bundled ruff. The lookup runs in this
|
|
@@ -38,12 +38,12 @@ remembered to pass.
|
|
|
38
38
|
| Command | Runs |
|
|
39
39
|
| --- | --- |
|
|
40
40
|
| `tempest-cli lint` | `ruff check` |
|
|
41
|
-
| `tempest-cli fix` | `ruff check --fix` then `ruff
|
|
41
|
+
| `tempest-cli fix` | `ruff check --fix-only`, `ruff format`, wraps long comments and splits long strings the formatter leaves alone, then a final `ruff check` that explains what is left (`--unsafe` for the risky autofixes) |
|
|
42
42
|
| `tempest-cli format` | `ruff format` (writes) |
|
|
43
43
|
| `tempest-cli fmt-check` | `ruff format --check` (read-only) |
|
|
44
44
|
| `tempest-cli type` | `mypy` |
|
|
45
|
-
| `tempest-cli test` | `pytest
|
|
46
|
-
| `tempest-cli check` | all four, in order, stopping at the first failure (
|
|
45
|
+
| `tempest-cli test` | `pytest`, in parallel by default with pytest-xdist (`-n auto -p no:cacheprovider`, `-w N` to choose; serial with a notice when it is not installed; `--serial` opts out) |
|
|
46
|
+
| `tempest-cli check` | all four, in order, stopping at the first failure (the test step in parallel as in `test`; a host CLI can add steps between `mypy` and `pytest`) |
|
|
47
47
|
| `tempest-cli pr-prompt` | builds the prompt that makes an AI write this branch's PR description |
|
|
48
48
|
|
|
49
49
|
Every command takes an optional path (`tempest-cli lint src/`) and
|
|
@@ -156,7 +156,8 @@ your suite. Add them yourself, or take the bundle:
|
|
|
156
156
|
uv add --dev "tempest-cli[tools]" # mypy + pytest + pytest-xdist
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
`pytest-xdist` is what `test
|
|
159
|
+
`pytest-xdist` is what `test` runs the suite in parallel with, by
|
|
160
|
+
default (without it the suite runs serially, with a notice); its
|
|
160
161
|
`requires-dist` (`execnet>=2.1`, `pytest>=7.0.0`) carries no upper bound.
|
|
161
162
|
|
|
162
163
|
Whatever you pin wins over the bundled ruff. The lookup runs in this
|
|
@@ -3,6 +3,41 @@
|
|
|
3
3
|
The full history lives in the repository's
|
|
4
4
|
[`CHANGELOG.md`](https://github.com/mauriciobenjamin700/tempest-cli/blob/main/CHANGELOG.md).
|
|
5
5
|
|
|
6
|
+
## [0.6.0] — 2026-10-20
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **`test` and `check` run the suite in parallel by default.** The
|
|
11
|
+
pytest-xdist `-n auto` run that needed `--fast` is now what a bare
|
|
12
|
+
`test` does; `--serial` opts out (for `--lf` / `--ff`, or a debugger).
|
|
13
|
+
Measured on a FastAPI suite of 1136 tests on 12 CPUs: 262 s serially,
|
|
14
|
+
37.6 s in parallel, same outcome.
|
|
15
|
+
[Details](commands.md#the-suite-in-parallel-the-default).
|
|
16
|
+
- **Without pytest-xdist the default degrades to serial instead of
|
|
17
|
+
failing**, with a notice naming the install command; only an explicit
|
|
18
|
+
`--fast` still requires the plugin (exit code 127).
|
|
19
|
+
- `--workers` with `--serial` is a usage error (exit code 2).
|
|
20
|
+
|
|
21
|
+
## [0.5.1] — 2026-10-15
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`fix` explains the E501 the formatter cannot resolve.** A line still
|
|
26
|
+
too long after `ruff format` (a dotted name, a string, a comment) comes
|
|
27
|
+
out with its token, its kind and the action that resolves it, and the
|
|
28
|
+
summary separates what has no autofix from what `--unsafe` would fix.
|
|
29
|
+
The exit code is unchanged.
|
|
30
|
+
[Details](commands.md#an-e501-ruff-format-cannot-fix).
|
|
31
|
+
- **`fix` shortens the long comments and strings the formatter leaves
|
|
32
|
+
alone.** A comment is wrapped at the limit and a string is split into
|
|
33
|
+
concatenated parts; each edit is kept only when the file's AST is
|
|
34
|
+
identical before and after. Pragmas, f-strings, triple quotes and long
|
|
35
|
+
words are left out and explained. On by default.
|
|
36
|
+
- **`fix` reports on the formatted file.** The passes are now
|
|
37
|
+
`ruff check --fix-only`, `ruff format` and `ruff check`; before, the
|
|
38
|
+
report and the exit code came from the pass that ran before the
|
|
39
|
+
formatter.
|
|
40
|
+
|
|
6
41
|
## [0.5.0] — 2026-10-13
|
|
7
42
|
|
|
8
43
|
### Added
|
|
@@ -26,7 +61,7 @@ The full history lives in the repository's
|
|
|
26
61
|
the default — or `logical`) and is a usage error without `--fast`. The
|
|
27
62
|
target is forwarded and pytest's exit code comes back untranslated. It
|
|
28
63
|
is a flag, not a subcommand, because `test fast` keeps meaning the
|
|
29
|
-
`fast/` folder. [Details](commands.md#the-suite-in-parallel-
|
|
64
|
+
`fast/` folder. [Details](commands.md#the-suite-in-parallel-the-default).
|
|
30
65
|
- **A missing pytest-xdist is a sentence, not `unrecognized arguments:
|
|
31
66
|
-n`.** `--fast` asks the interpreter that will run pytest whether it
|
|
32
67
|
can import `xdist`; when it cannot, the message names the package and
|
|
@@ -4,6 +4,39 @@ O histórico completo vive no
|
|
|
4
4
|
[`CHANGELOG.md`](https://github.com/mauriciobenjamin700/tempest-cli/blob/main/CHANGELOG.md)
|
|
5
5
|
do repositório.
|
|
6
6
|
|
|
7
|
+
## [0.6.0] — 2026-10-20
|
|
8
|
+
|
|
9
|
+
### Alterado
|
|
10
|
+
|
|
11
|
+
- **O `test` e o `check` rodam a suíte em paralelo por padrão.** O
|
|
12
|
+
`-n auto` do pytest-xdist, que pedia `--fast`, agora é o que um
|
|
13
|
+
`test` puro faz; `--serial` desliga (para `--lf` / `--ff` ou depurador).
|
|
14
|
+
Medido numa suíte FastAPI de 1136 testes em 12 CPUs: 262 s em série,
|
|
15
|
+
37,6 s em paralelo, mesmo resultado.
|
|
16
|
+
[Detalhes](commands.md#a-suite-em-paralelo-o-padrao).
|
|
17
|
+
- **Sem o pytest-xdist, o padrão cai para série em vez de falhar**, com um
|
|
18
|
+
aviso de como instalar; só o `--fast` explícito continua exigindo o
|
|
19
|
+
plugin (código de saída 127).
|
|
20
|
+
- `--workers` com `--serial` é erro de uso (código de saída 2).
|
|
21
|
+
|
|
22
|
+
## [0.5.1] — 2026-10-15
|
|
23
|
+
|
|
24
|
+
### Corrigido
|
|
25
|
+
|
|
26
|
+
- **O `fix` explica o E501 que o formatador não resolve.** Linha ainda
|
|
27
|
+
longa depois do `ruff format` (nome pontuado, string, comentário) sai
|
|
28
|
+
com o token, o tipo e a ação que resolve, e o resumo separa o que não
|
|
29
|
+
tem autofix do que o `--unsafe` resolveria. O código de saída não muda.
|
|
30
|
+
[Detalhes](commands.md#e501-que-o-ruff-format-nao-resolve).
|
|
31
|
+
- **O `fix` encurta comentário e string longos que o formatador não
|
|
32
|
+
quebra.** Comentário é quebrado no limite e string é partida em pedaços
|
|
33
|
+
concatenados; cada edição só fica se a AST do arquivo for idêntica
|
|
34
|
+
antes e depois. Pragma, f-string, três aspas e palavra longa ficam de
|
|
35
|
+
fora e são explicados. Ligado por padrão.
|
|
36
|
+
- **O `fix` relata sobre o arquivo formatado.** As passadas agora são
|
|
37
|
+
`ruff check --fix-only`, `ruff format` e `ruff check`; antes, o
|
|
38
|
+
relatório e o código de saída vinham da passada anterior ao formatador.
|
|
39
|
+
|
|
7
40
|
## [0.5.0] — 2026-10-13
|
|
8
41
|
|
|
9
42
|
### Adicionado
|
|
@@ -27,7 +60,7 @@ do repositório.
|
|
|
27
60
|
o padrão — ou `logical`) e é erro de uso sem `--fast`. O alvo é
|
|
28
61
|
repassado e o código de saída do pytest volta sem tradução. É flag, e
|
|
29
62
|
não subcomando, porque `test fast` continua significando a pasta
|
|
30
|
-
`fast/`. [Detalhes](commands.md#a-suite-em-paralelo-
|
|
63
|
+
`fast/`. [Detalhes](commands.md#a-suite-em-paralelo-o-padrao).
|
|
31
64
|
- **pytest-xdist ausente vira frase, não `unrecognized arguments: -n`.**
|
|
32
65
|
O `--fast` pergunta ao interpretador que vai rodar o pytest se ele
|
|
33
66
|
importa o `xdist`; faltando, a mensagem nomeia o pacote e os extras
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Commands
|
|
2
|
+
|
|
3
|
+
Eight commands. All take an optional path and return the underlying
|
|
4
|
+
tool's exit code.
|
|
5
|
+
|
|
6
|
+
| Command | Runs |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| `lint` | `ruff check` |
|
|
9
|
+
| `fix` | `ruff check --fix-only`, `ruff format`, shortens long comments/strings, then a final `ruff check` explaining what is left |
|
|
10
|
+
| `format` | `ruff format` (writes) |
|
|
11
|
+
| `fmt-check` | `ruff format --check` (read-only) |
|
|
12
|
+
| `type` | `mypy` |
|
|
13
|
+
| `test` | `pytest` |
|
|
14
|
+
| `check` | the four above, in order, stopping at the first failure (a CLI that mounts the gate can [add steps](library.md#adding-steps-to-check) between `mypy` and `pytest`) |
|
|
15
|
+
| `pr-prompt` | builds the PR-description prompt — [its own page](pr-prompt.md) |
|
|
16
|
+
|
|
17
|
+
!!! tip "`tc` is the short form"
|
|
18
|
+
The package installs `tempest-cli` and `tc` pointing at the same
|
|
19
|
+
program. The examples use the long name; `tc check`, `tc fix` and
|
|
20
|
+
`tc type -s strict` work exactly the same.
|
|
21
|
+
|
|
22
|
+
## The full gate
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
tempest-cli check # the whole project
|
|
26
|
+
tempest-cli check src/ # a single path
|
|
27
|
+
tempest-cli check -s strict # with raised typing strictness for this run
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The order is deliberate: **lint → formatting → types → tests**. What
|
|
31
|
+
fails fastest and is cheapest to fix comes first, and the run stops at
|
|
32
|
+
the first failure — there is no point running the whole suite when the
|
|
33
|
+
imports are unsorted.
|
|
34
|
+
|
|
35
|
+
## Fixing what can be fixed
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tempest-cli fix # safe autofixes + formatting
|
|
39
|
+
tempest-cli fix --unsafe # also ruff's risky autofixes
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`fix` makes four passes:
|
|
43
|
+
|
|
44
|
+
1. `ruff check --fix-only` — sorts and dedupes imports, drops unused
|
|
45
|
+
ones, normalizes quotes.
|
|
46
|
+
2. `ruff format` — indentation, line length, blank lines.
|
|
47
|
+
3. Shortens the long lines the formatter cannot break, when that can be
|
|
48
|
+
done without changing the program: comments and strings (see below).
|
|
49
|
+
4. `ruff check` — the report of what is left, on the final file.
|
|
50
|
+
|
|
51
|
+
The exit code is the last pass's: anything left, `fix` exits with an
|
|
52
|
+
error and CI fails.
|
|
53
|
+
|
|
54
|
+
!!! warning "`--unsafe` can change behavior"
|
|
55
|
+
Ruff's "unsafe" autofixes are the ones that may alter semantics.
|
|
56
|
+
They are off by default. Turned them on? Read the `git diff` before
|
|
57
|
+
committing.
|
|
58
|
+
|
|
59
|
+
### An E501 `ruff format` cannot fix
|
|
60
|
+
|
|
61
|
+
Sometimes `fix` ends like this, even with the right `line-length` in
|
|
62
|
+
`pyproject.toml`:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
class AloController:
|
|
66
|
+
def template(self, kind: str) -> str:
|
|
67
|
+
match kind:
|
|
68
|
+
case "waiting":
|
|
69
|
+
user_template = notifications.NOTIFICATION_USER_BODY_ALO_STATUS_CHANGE_WAITING
|
|
70
|
+
return user_template
|
|
71
|
+
return ""
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
E501 Line too long (94 > 79)
|
|
76
|
+
--> controller.py:5:80
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
It is not a wrong setting — `fix` passes no `line-length` to ruff, the
|
|
80
|
+
project's value already applies. It is a limit of the formatter: it
|
|
81
|
+
**never splits a dotted name** (`a.b.C`), and the parentheses you put
|
|
82
|
+
around it (`x = (\n a.b.C\n)`) go away, because they do not make the
|
|
83
|
+
line fit. 🤷
|
|
84
|
+
|
|
85
|
+
#### What `fix` shortens on its own
|
|
86
|
+
|
|
87
|
+
Two of these cases have a single right answer, and `fix` applies it:
|
|
88
|
+
|
|
89
|
+
- a **comment** is wrapped at the limit; a trailing one moves above the
|
|
90
|
+
code it annotates;
|
|
91
|
+
- a **string** is split at its spaces into concatenated parts inside
|
|
92
|
+
parentheses — Python joins them back into the same value.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# before
|
|
96
|
+
text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb" # the text
|
|
97
|
+
|
|
98
|
+
# after fix
|
|
99
|
+
# the text
|
|
100
|
+
text = (
|
|
101
|
+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
|
|
102
|
+
"bbbbbbbbbbbbbbbb"
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
shortened 1 long line(s) ruff format cannot break (comments wrapped, strings split into concatenated parts):
|
|
108
|
+
mod.py:2 [comment+string]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
!!! check "Only what is proven equal goes in"
|
|
112
|
+
Before writing, `fix` compares the file's AST before and after.
|
|
113
|
+
Comments never reach the AST and the parser folds concatenated
|
|
114
|
+
strings into one constant, so a correct rewrite always passes.
|
|
115
|
+
Anything else — a `\N{...}` cut in half, a comment pushed into a
|
|
116
|
+
`\` continuation — changes the AST, and the line stays as it was.
|
|
117
|
+
|
|
118
|
+
Left out on purpose: tool comments (`noqa`, `type: ignore`, `fmt:`,
|
|
119
|
+
`pragma`…), which must stay on the line they govern; f-strings and
|
|
120
|
+
triple-quoted strings; and a word longer than a line (a URL, say).
|
|
121
|
+
|
|
122
|
+
#### What is left
|
|
123
|
+
|
|
124
|
+
Dotted names, long names and expressions have no mechanical rewrite —
|
|
125
|
+
and splitting them by hand is undone by the next `ruff format`. For
|
|
126
|
+
those, `fix` explains each line right below ruff's report:
|
|
127
|
+
|
|
128
|
+
```text
|
|
129
|
+
1 line(s) still too long after ruff format — the line-length setting was applied; the formatter has nowhere to break these:
|
|
130
|
+
controller.py:5 [dotted-name] `notifications.NOTIFICATION_USER_BODY_ALO_STATUS_CHANGE_WAITING`
|
|
131
|
+
ruff format never splits a dotted name, and drops parentheses that do not make the line fit. Shorten the expression: import or alias the attribute, move the choice into a table or constant, or lower the indentation.
|
|
132
|
+
1 residual violation(s): 1 with no autofix (edit by hand).
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The bracketed label says what sits where the line crosses the limit:
|
|
136
|
+
|
|
137
|
+
| Label | What it is | What resolves it |
|
|
138
|
+
| --- | --- | --- |
|
|
139
|
+
| `dotted-name` | a dotted name (`module.CONSTANT`) | an alias, a table/constant, less indentation |
|
|
140
|
+
| `name` | one name that is too long on its own | rename it or indent less |
|
|
141
|
+
| `string` | a string `fix` could not split (f-string, triple quotes, long word) | split it by hand into concatenated parts |
|
|
142
|
+
| `comment` | a comment `fix` could not wrap (pragma, long word) | wrap it by hand or move it into the docstring |
|
|
143
|
+
| `expression` | anything else | extract a variable or indent less |
|
|
144
|
+
|
|
145
|
+
The last line separates what has **no autofix** from what `fix --unsafe`
|
|
146
|
+
would resolve — without it, `Found 5 errors` does not say whether
|
|
147
|
+
another run is worth it.
|
|
148
|
+
|
|
149
|
+
!!! tip "Move the choice into a table"
|
|
150
|
+
In the example above, the indentation comes from the `match`.
|
|
151
|
+
Replacing it with a module-level dictionary removes four levels and
|
|
152
|
+
the line fits in exactly 79, without an alias:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
import notifications
|
|
156
|
+
|
|
157
|
+
TEMPLATES: dict[str, str] = {
|
|
158
|
+
"waiting": notifications.NOTIFICATION_USER_BODY_ALO_STATUS_CHANGE_WAITING,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class AloController:
|
|
163
|
+
def template(self, kind: str) -> str:
|
|
164
|
+
return TEMPLATES.get(kind, "")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
!!! note "What about `# noqa: E501`?"
|
|
168
|
+
It works, but hides the problem until the next refactor drops the
|
|
169
|
+
`noqa` — which is exactly how this case showed up. Prefer changing
|
|
170
|
+
the shape of the code.
|
|
171
|
+
|
|
172
|
+
## One at a time
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
tempest-cli lint src/
|
|
176
|
+
tempest-cli fmt-check
|
|
177
|
+
tempest-cli type mypackage
|
|
178
|
+
tempest-cli test tests/unit
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`test` forwards the path to pytest as a filter; with no argument it runs
|
|
182
|
+
the whole suite.
|
|
183
|
+
|
|
184
|
+
## The suite in parallel (the default)
|
|
185
|
+
|
|
186
|
+
A suite whose tests are already isolated (a database per test, no global
|
|
187
|
+
state) pays the serial time and buys nothing for it. That is why `test`
|
|
188
|
+
and `check` spread the suite across cores with
|
|
189
|
+
[pytest-xdist](https://pytest-xdist.readthedocs.io/) **without being
|
|
190
|
+
asked**:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
tempest-cli test # -n auto: one worker per core
|
|
194
|
+
tempest-cli test -w 4 # four workers
|
|
195
|
+
tempest-cli test tests/unit # the target is still forwarded
|
|
196
|
+
tempest-cli check # the full gate, with the test step in parallel
|
|
197
|
+
tempest-cli test --serial # a single process (for --lf / --ff, or a debugger)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Underneath it becomes `pytest -n <workers> -p no:cacheprovider [target]`.
|
|
201
|
+
`-p no:cacheprovider` is there because several workers writing
|
|
202
|
+
`.pytest_cache` at once is a race that buys nothing — and that cache is
|
|
203
|
+
what `--lf` / `--ff` read, so those two are a `--serial` affair.
|
|
204
|
+
|
|
205
|
+
On a FastAPI service suite of 1136 tests (an in-memory SQLite database
|
|
206
|
+
per test), on a 12-CPU machine: **262 s** serially, **37.6 s** with
|
|
207
|
+
`-n auto` — same outcome, 1136 passed.
|
|
208
|
+
|
|
209
|
+
`--workers` (`-w`) takes what `pytest -n` takes: an integer, `auto` (the
|
|
210
|
+
default) or `logical`. With `--serial` it is a usage error (exit 2)
|
|
211
|
+
instead of being silently ignored.
|
|
212
|
+
|
|
213
|
+
!!! info "`auto` counts physical cores when `psutil` is installed"
|
|
214
|
+
pytest-xdist decides: with `psutil` importable, `auto` is the number of
|
|
215
|
+
**physical** cores; without it, the number of logical CPUs. On a
|
|
216
|
+
6-core, 12-thread machine with `psutil` in the environment, `auto`
|
|
217
|
+
reported `created: 6/6 workers`. Want the 12 threads? `-w logical` or
|
|
218
|
+
`-w 12`.
|
|
219
|
+
|
|
220
|
+
!!! note "Why a flag, and not `tempest-cli test serial`"
|
|
221
|
+
`test` already takes a positional path. `test fast` keeps meaning "run
|
|
222
|
+
pytest on the `fast/` folder" — a subcommand by that name would break
|
|
223
|
+
anyone who has that folder.
|
|
224
|
+
|
|
225
|
+
### Without pytest-xdist
|
|
226
|
+
|
|
227
|
+
Before running anything, the CLI asks **the very interpreter that will
|
|
228
|
+
run pytest** whether it can import `xdist` — not the CLI's own
|
|
229
|
+
interpreter, which under `uv tool install` or pipx lives in another
|
|
230
|
+
environment. When it is missing, it depends on who asked for parallel:
|
|
231
|
+
|
|
232
|
+
- **Nobody (the default):** the suite runs serially, with a notice on
|
|
233
|
+
stderr naming what to install. `tempest-cli test` keeps working in a
|
|
234
|
+
project that never installed pytest-xdist — it is just slower.
|
|
235
|
+
|
|
236
|
+
```console
|
|
237
|
+
$ tempest-cli test
|
|
238
|
+
note: pytest-xdist is not installed in the environment pytest runs in (.venv/bin/pytest), so the suite runs serially. Install it with 'uv add --dev pytest-xdist' — or a bundle that carries it, 'uv add --dev "tempest-cli[tools]"' or 'uv add --dev "tempest-fastapi-sdk[tests]"' — to run it in parallel.
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- **`--fast` by name:** parallel becomes a requirement. The message names
|
|
242
|
+
what to install and the exit code is **127**, with nothing run:
|
|
243
|
+
|
|
244
|
+
```console
|
|
245
|
+
$ tempest-cli test --fast
|
|
246
|
+
error: --fast needs pytest-xdist, which is not installed in the environment pytest runs in (.venv/bin/pytest). Install it with 'uv add --dev pytest-xdist' — or a bundle that carries it, 'uv add --dev "tempest-cli[tools]"' or 'uv add --dev "tempest-fastapi-sdk[tests]"' — and retry, or pass --serial to run the suite serially.
|
|
247
|
+
$ echo $?
|
|
248
|
+
127
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Under `check` the probe happens **before** the first step: with `--fast`
|
|
252
|
+
there is no point waiting for lint and mypy to finish only to learn the
|
|
253
|
+
tests will not run.
|
|
254
|
+
|
|
255
|
+
### A test that fails only in parallel
|
|
256
|
+
|
|
257
|
+
Parallelism exposes tests that depend on order or on an idle machine: two
|
|
258
|
+
tests writing the same file, the same port, a module-level global, a
|
|
259
|
+
fixed `sleep` waiting for something that takes longer with every core
|
|
260
|
+
busy. Before treating the failure as a regression, run the test **alone
|
|
261
|
+
and serially**:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
tempest-cli test --serial "tests/test_scheduler.py::test_lease_expires"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
- **Passes alone**: the defect is the test's isolation, not the change
|
|
268
|
+
under review. Fix the test (a file under `tmp_path`, a free port, wait
|
|
269
|
+
on a condition instead of `sleep`).
|
|
270
|
+
- **Fails alone too**: it is a real regression.
|
|
271
|
+
|
|
272
|
+
## What each returns
|
|
273
|
+
|
|
274
|
+
The exit code is the tool's own, untranslated:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
tempest-cli lint; echo "exited $?"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
There is exactly one code of its own: **127**, when the tool is in none
|
|
281
|
+
of the places the CLI looks — the run's environment, `PATH`, `uv run
|
|
282
|
+
--with` — and the message then names what was missing and how to install
|
|
283
|
+
it. The lookup order is in
|
|
284
|
+
[Installation](installation.md#where-ruff-mypy-and-pytest-come-from).
|
|
285
|
+
The same 127 comes out of an explicit `--fast` when pytest-xdist is not in
|
|
286
|
+
pytest's environment; without the flag, its absence only makes the suite
|
|
287
|
+
serial.
|
|
288
|
+
|
|
289
|
+
## Recap
|
|
290
|
+
|
|
291
|
+
- `check` is lint + fmt-check + type + test, in order, stopping at the
|
|
292
|
+
first failure.
|
|
293
|
+
- `fix` is the pass that repairs; `--unsafe` only when you will review.
|
|
294
|
+
- Long comments and strings `fix` shortens on its own, with the AST
|
|
295
|
+
proven equal. An E501 left after it has no mechanical rewrite: `fix`
|
|
296
|
+
names the token and the action — change the code's shape.
|
|
297
|
+
- `test` and `check` run the suite in parallel by default, with
|
|
298
|
+
pytest-xdist (`-n auto`, `-w N` to choose); `--serial` turns it off.
|
|
299
|
+
Without pytest-xdist the default falls back to serial with a notice,
|
|
300
|
+
and an explicit `--fast` becomes exit 127. A test that fails only in
|
|
301
|
+
parallel is checked by running it alone, with `--serial`.
|
|
302
|
+
- Exit codes are the tools'; 127 means a missing tool (or the
|
|
303
|
+
pytest-xdist an explicit `--fast` requires).
|