tempest-cli 0.4.0__tar.gz → 0.5.1__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.4.0 → tempest_cli-0.5.1}/CHANGELOG.md +49 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/PKG-INFO +25 -3
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/README.md +24 -2
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/changelog.en.md +33 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/changelog.md +31 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/commands.en.md +129 -5
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/commands.md +129 -5
- tempest_cli-0.5.1/docs/library.en.md +193 -0
- tempest_cli-0.5.1/docs/library.md +192 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/reference.md +6 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/pyproject.toml +1 -1
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/__init__.py +8 -1
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/lint.py +131 -35
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/main.py +44 -6
- tempest_cli-0.5.1/tempest_cli/residuals.py +356 -0
- tempest_cli-0.5.1/tempest_cli/rewrite.py +417 -0
- tempest_cli-0.5.1/tempest_cli/steps.py +54 -0
- tempest_cli-0.5.1/tests/test_extra_steps.py +201 -0
- tempest_cli-0.5.1/tests/test_fix_residuals.py +194 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tests/test_lint_runners.py +4 -3
- tempest_cli-0.5.1/tests/test_rewrite.py +222 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/uv.lock +1 -1
- tempest_cli-0.4.0/docs/library.en.md +0 -97
- tempest_cli-0.4.0/docs/library.md +0 -97
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/.github/workflows/ci.yml +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/.github/workflows/docs.yml +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/.github/workflows/release-pypi.yml +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/.gitignore +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/LICENSE +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/Makefile +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/configuration.en.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/configuration.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/index.en.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/index.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/installation.en.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/installation.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/pr-prompt.en.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/pr-prompt.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/reference.en.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/rules.en.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/docs/rules.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/mkdocs.yml +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/_templates/pull_request_template.en-US.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/_templates/pull_request_template.pt-BR.md +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/config.py +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/pr_prompt.py +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tempest_cli/py.typed +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tests/__init__.py +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tests/test_cli.py +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tests/test_config.py +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tests/test_fast.py +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tests/test_lint_strictness.py +0 -0
- {tempest_cli-0.4.0 → tempest_cli-0.5.1}/tests/test_pr_prompt.py +0 -0
|
@@ -5,6 +5,55 @@ 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
|
+
## [0.5.1] — 2026-10-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`fix` explains the E501 the formatter cannot resolve.** A line still
|
|
13
|
+
too long after `ruff format` is one the formatter has nowhere to break
|
|
14
|
+
— a dotted name (it never splits one, and drops parentheses that do not
|
|
15
|
+
make the line fit), a string literal, a comment — but the bare
|
|
16
|
+
`Line too long (94 > 79)` read as if `line-length` were being ignored.
|
|
17
|
+
`fix` now reads ruff's JSON report of the leftovers and names each such
|
|
18
|
+
line with the overflowing token, its kind and the action that does
|
|
19
|
+
resolve it, then splits the residual violations into those with no
|
|
20
|
+
autofix and those `fix --unsafe` would fix. The exit code is unchanged:
|
|
21
|
+
CI still fails on them. Closes #3.
|
|
22
|
+
- **`fix` shortens the long comments and strings the formatter leaves
|
|
23
|
+
alone.** After `ruff format`, each E501 that is a comment is wrapped at
|
|
24
|
+
the limit (a trailing one moves above its line) and each that is a
|
|
25
|
+
plain string literal is split at its spaces into implicitly
|
|
26
|
+
concatenated parts inside parentheses; the touched files are formatted
|
|
27
|
+
again. Every edit is kept only when the file's AST is identical before
|
|
28
|
+
and after, so the program cannot change. Tool pragmas (`noqa`,
|
|
29
|
+
`type: ignore`, `fmt:`, …), f-strings, triple-quoted strings and words
|
|
30
|
+
longer than a line are left alone and explained instead. On by
|
|
31
|
+
default: neither rewrite changes behavior.
|
|
32
|
+
- **`fix` reports on the formatted file.** The passes are now
|
|
33
|
+
`ruff check --fix-only`, `ruff format`, then `ruff check`; before, the
|
|
34
|
+
report and the exit code came from the lint pass that ran *before* the
|
|
35
|
+
formatter, so a violation the formatter went on to resolve still showed
|
|
36
|
+
up and still failed the command.
|
|
37
|
+
|
|
38
|
+
## [0.5.0] — 2026-10-13
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **`extra_steps`: a framework's own rules inside `check`.** The new
|
|
43
|
+
`CheckStep(name, run)` (frozen dataclass, exported from `tempest_cli`)
|
|
44
|
+
is a step `check` runs after `mypy` and before `pytest`: `run` receives
|
|
45
|
+
the `check` target and returns an exit code. `run_full_check(...,
|
|
46
|
+
extra_steps=)` and `register_commands(app, *, extra_steps=)` take a
|
|
47
|
+
sequence of them — keyword-only, empty by default, so every existing
|
|
48
|
+
call runs the same four steps as before. Each extra step is echoed as
|
|
49
|
+
`$ <name>` on stderr, runs in list order, and the first non-zero code
|
|
50
|
+
stops the gate and becomes its exit code, with or without `--fast`.
|
|
51
|
+
They sit before `pytest` because they are static checks that should
|
|
52
|
+
fail in a second, not after the suite. Requested in
|
|
53
|
+
mauriciobenjamin700/tempest-fastapi-sdk#520, where the SDK adds its
|
|
54
|
+
service-layer rules to `tempest check` without this package learning
|
|
55
|
+
about any framework.
|
|
56
|
+
|
|
8
57
|
## [0.4.0] — 2026-09-27
|
|
9
58
|
|
|
10
59
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tempest-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
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
77
|
| `tempest-cli test` | `pytest` (`--fast` spreads it across cores with pytest-xdist: `-n auto -p no:cacheprovider`, `-w N` to choose) |
|
|
78
|
-
| `tempest-cli check` | all four, in order, stopping at the first failure (`--fast` runs the test step in parallel) |
|
|
78
|
+
| `tempest-cli check` | all four, in order, stopping at the first failure (`--fast` runs the test step in parallel; 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
|
|
@@ -150,6 +150,28 @@ cli: typer.Typer = typer.Typer(name="mytool")
|
|
|
150
150
|
register_commands(cli)
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
A framework that wants its own rules in `check` passes them as
|
|
154
|
+
`CheckStep`s — they run after `mypy` and before `pytest`, and the first
|
|
155
|
+
non-zero exit code stops the gate:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
import typer
|
|
159
|
+
|
|
160
|
+
from tempest_cli import CheckStep
|
|
161
|
+
from tempest_cli.main import register_commands
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def layers(target: str) -> int:
|
|
165
|
+
"""Return 0 when every module under ``target`` follows the layering."""
|
|
166
|
+
return 0
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
cli: typer.Typer = typer.Typer(name="mytool")
|
|
170
|
+
register_commands(cli, extra_steps=[CheckStep(name="layers", run=layers)])
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`run_full_check(".", extra_steps=[...])` takes the same list.
|
|
174
|
+
|
|
153
175
|
## Where the tools come from
|
|
154
176
|
|
|
155
177
|
**`ruff` comes with the package** — six of the eight commands are ruff,
|
|
@@ -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
45
|
| `tempest-cli test` | `pytest` (`--fast` spreads it across cores with pytest-xdist: `-n auto -p no:cacheprovider`, `-w N` to choose) |
|
|
46
|
-
| `tempest-cli check` | all four, in order, stopping at the first failure (`--fast` runs the test step in parallel) |
|
|
46
|
+
| `tempest-cli check` | all four, in order, stopping at the first failure (`--fast` runs the test step in parallel; 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
|
|
@@ -118,6 +118,28 @@ cli: typer.Typer = typer.Typer(name="mytool")
|
|
|
118
118
|
register_commands(cli)
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
A framework that wants its own rules in `check` passes them as
|
|
122
|
+
`CheckStep`s — they run after `mypy` and before `pytest`, and the first
|
|
123
|
+
non-zero exit code stops the gate:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import typer
|
|
127
|
+
|
|
128
|
+
from tempest_cli import CheckStep
|
|
129
|
+
from tempest_cli.main import register_commands
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def layers(target: str) -> int:
|
|
133
|
+
"""Return 0 when every module under ``target`` follows the layering."""
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
cli: typer.Typer = typer.Typer(name="mytool")
|
|
138
|
+
register_commands(cli, extra_steps=[CheckStep(name="layers", run=layers)])
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`run_full_check(".", extra_steps=[...])` takes the same list.
|
|
142
|
+
|
|
121
143
|
## Where the tools come from
|
|
122
144
|
|
|
123
145
|
**`ruff` comes with the package** — six of the eight commands are ruff,
|
|
@@ -3,6 +3,39 @@
|
|
|
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.5.1] — 2026-10-15
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **`fix` explains the E501 the formatter cannot resolve.** A line still
|
|
11
|
+
too long after `ruff format` (a dotted name, a string, a comment) comes
|
|
12
|
+
out with its token, its kind and the action that resolves it, and the
|
|
13
|
+
summary separates what has no autofix from what `--unsafe` would fix.
|
|
14
|
+
The exit code is unchanged.
|
|
15
|
+
[Details](commands.md#an-e501-ruff-format-cannot-fix).
|
|
16
|
+
- **`fix` shortens the long comments and strings the formatter leaves
|
|
17
|
+
alone.** A comment is wrapped at the limit and a string is split into
|
|
18
|
+
concatenated parts; each edit is kept only when the file's AST is
|
|
19
|
+
identical before and after. Pragmas, f-strings, triple quotes and long
|
|
20
|
+
words are left out and explained. On by default.
|
|
21
|
+
- **`fix` reports on the formatted file.** The passes are now
|
|
22
|
+
`ruff check --fix-only`, `ruff format` and `ruff check`; before, the
|
|
23
|
+
report and the exit code came from the pass that ran before the
|
|
24
|
+
formatter.
|
|
25
|
+
|
|
26
|
+
## [0.5.0] — 2026-10-13
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **`extra_steps`: a framework's own rules inside `check`.** The new
|
|
31
|
+
`CheckStep(name, run)` is a step `check` runs after `mypy` and before
|
|
32
|
+
`pytest`; `run` receives the target and returns an exit code.
|
|
33
|
+
`run_full_check(..., extra_steps=)` and
|
|
34
|
+
`register_commands(app, *, extra_steps=)` take the list — empty by
|
|
35
|
+
default, so nothing changes for a caller that does not pass one. The
|
|
36
|
+
first non-zero code stops the gate, with or without `--fast`.
|
|
37
|
+
[Details](library.md#adding-steps-to-check).
|
|
38
|
+
|
|
6
39
|
## [0.4.0] — 2026-09-27
|
|
7
40
|
|
|
8
41
|
### Added
|
|
@@ -4,6 +4,37 @@ 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.5.1] — 2026-10-15
|
|
8
|
+
|
|
9
|
+
### Corrigido
|
|
10
|
+
|
|
11
|
+
- **O `fix` explica o E501 que o formatador não resolve.** Linha ainda
|
|
12
|
+
longa depois do `ruff format` (nome pontuado, string, comentário) sai
|
|
13
|
+
com o token, o tipo e a ação que resolve, e o resumo separa o que não
|
|
14
|
+
tem autofix do que o `--unsafe` resolveria. O código de saída não muda.
|
|
15
|
+
[Detalhes](commands.md#e501-que-o-ruff-format-nao-resolve).
|
|
16
|
+
- **O `fix` encurta comentário e string longos que o formatador não
|
|
17
|
+
quebra.** Comentário é quebrado no limite e string é partida em pedaços
|
|
18
|
+
concatenados; cada edição só fica se a AST do arquivo for idêntica
|
|
19
|
+
antes e depois. Pragma, f-string, três aspas e palavra longa ficam de
|
|
20
|
+
fora e são explicados. Ligado por padrão.
|
|
21
|
+
- **O `fix` relata sobre o arquivo formatado.** As passadas agora são
|
|
22
|
+
`ruff check --fix-only`, `ruff format` e `ruff check`; antes, o
|
|
23
|
+
relatório e o código de saída vinham da passada anterior ao formatador.
|
|
24
|
+
|
|
25
|
+
## [0.5.0] — 2026-10-13
|
|
26
|
+
|
|
27
|
+
### Adicionado
|
|
28
|
+
|
|
29
|
+
- **`extra_steps`: regras do framework dentro do `check`.** O novo
|
|
30
|
+
`CheckStep(name, run)` é um passo que o `check` roda depois do `mypy` e
|
|
31
|
+
antes do `pytest`; `run` recebe o alvo e devolve um código de saída.
|
|
32
|
+
`run_full_check(..., extra_steps=)` e
|
|
33
|
+
`register_commands(app, *, extra_steps=)` aceitam a lista — vazia por
|
|
34
|
+
padrão, então nada muda para quem não passa. O primeiro código diferente
|
|
35
|
+
de `0` para o gate, com ou sem `--fast`.
|
|
36
|
+
[Detalhes](library.md#acrescentar-passos-ao-check).
|
|
37
|
+
|
|
7
38
|
## [0.4.0] — 2026-09-27
|
|
8
39
|
|
|
9
40
|
### Adicionado
|
|
@@ -6,12 +6,12 @@ tool's exit code.
|
|
|
6
6
|
| Command | Runs |
|
|
7
7
|
| --- | --- |
|
|
8
8
|
| `lint` | `ruff check` |
|
|
9
|
-
| `fix` | `ruff check --fix` then `ruff
|
|
9
|
+
| `fix` | `ruff check --fix-only`, `ruff format`, shortens long comments/strings, then a final `ruff check` explaining what is left |
|
|
10
10
|
| `format` | `ruff format` (writes) |
|
|
11
11
|
| `fmt-check` | `ruff format --check` (read-only) |
|
|
12
12
|
| `type` | `mypy` |
|
|
13
13
|
| `test` | `pytest` |
|
|
14
|
-
| `check` | the four above, in order, stopping at the first failure |
|
|
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
15
|
| `pr-prompt` | builds the PR-description prompt — [its own page](pr-prompt.md) |
|
|
16
16
|
|
|
17
17
|
!!! tip "`tc` is the short form"
|
|
@@ -39,15 +39,136 @@ tempest-cli fix # safe autofixes + formatting
|
|
|
39
39
|
tempest-cli fix --unsafe # also ruff's risky autofixes
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
`fix` makes
|
|
43
|
-
|
|
44
|
-
|
|
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.
|
|
45
53
|
|
|
46
54
|
!!! warning "`--unsafe` can change behavior"
|
|
47
55
|
Ruff's "unsafe" autofixes are the ones that may alter semantics.
|
|
48
56
|
They are off by default. Turned them on? Read the `git diff` before
|
|
49
57
|
committing.
|
|
50
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
|
+
|
|
51
172
|
## One at a time
|
|
52
173
|
|
|
53
174
|
```bash
|
|
@@ -152,6 +273,9 @@ environment.
|
|
|
152
273
|
- `check` is lint + fmt-check + type + test, in order, stopping at the
|
|
153
274
|
first failure.
|
|
154
275
|
- `fix` is the pass that repairs; `--unsafe` only when you will review.
|
|
276
|
+
- Long comments and strings `fix` shortens on its own, with the AST
|
|
277
|
+
proven equal. An E501 left after it has no mechanical rewrite: `fix`
|
|
278
|
+
names the token and the action — change the code's shape.
|
|
155
279
|
- `test --fast` / `check --fast` run the suite with pytest-xdist
|
|
156
280
|
(`-n auto`, `-w N` to choose); a test that fails only in parallel is
|
|
157
281
|
checked by running it alone.
|
|
@@ -6,12 +6,12 @@ saída da ferramenta por baixo.
|
|
|
6
6
|
| Comando | Roda |
|
|
7
7
|
| --- | --- |
|
|
8
8
|
| `lint` | `ruff check` |
|
|
9
|
-
| `fix` | `ruff check --fix` e
|
|
9
|
+
| `fix` | `ruff check --fix-only`, `ruff format`, encurta comentário/string longos e um `ruff check` final que explica o que sobrou |
|
|
10
10
|
| `format` | `ruff format` (escreve) |
|
|
11
11
|
| `fmt-check` | `ruff format --check` (só leitura) |
|
|
12
12
|
| `type` | `mypy` |
|
|
13
13
|
| `test` | `pytest` |
|
|
14
|
-
| `check` | os quatro acima, em ordem, parando na primeira falha |
|
|
14
|
+
| `check` | os quatro acima, em ordem, parando na primeira falha (uma CLI que monta o gate pode [acrescentar passos](library.md#acrescentar-passos-ao-check) entre o `mypy` e o `pytest`) |
|
|
15
15
|
| `pr-prompt` | monta o prompt da descrição do PR — [página própria](pr-prompt.md) |
|
|
16
16
|
|
|
17
17
|
!!! tip "`tc` é a forma curta"
|
|
@@ -39,15 +39,136 @@ tempest-cli fix # autofixes seguros + formatação
|
|
|
39
39
|
tempest-cli fix --unsafe # inclui os autofixes de risco do ruff
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
O `fix` faz
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
O `fix` faz quatro passadas:
|
|
43
|
+
|
|
44
|
+
1. `ruff check --fix-only` — ordena e deduplica imports, remove imports
|
|
45
|
+
não usados, normaliza aspas.
|
|
46
|
+
2. `ruff format` — indentação, comprimento de linha, linhas em branco.
|
|
47
|
+
3. Encurta as linhas longas que o formatador não quebra, quando dá para
|
|
48
|
+
fazer isso sem mudar o programa: comentários e strings (veja abaixo).
|
|
49
|
+
4. `ruff check` — o relatório do que sobrou, já sobre o arquivo final.
|
|
50
|
+
|
|
51
|
+
O código de saída é o da última passada: sobrou violação, o `fix` sai
|
|
52
|
+
com erro e o CI falha.
|
|
45
53
|
|
|
46
54
|
!!! warning "`--unsafe` muda comportamento"
|
|
47
55
|
Os autofixes "unsafe" do ruff são os que podem alterar semântica.
|
|
48
56
|
Ficam desligados por padrão. Ligou? Leia o `git diff` antes de
|
|
49
57
|
commitar.
|
|
50
58
|
|
|
59
|
+
### E501 que o `ruff format` não resolve
|
|
60
|
+
|
|
61
|
+
Às vezes o `fix` termina assim, mesmo com o `line-length` certo no
|
|
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
|
+
Não é configuração errada — o `fix` não passa `line-length` nenhum ao
|
|
80
|
+
ruff, o valor do projeto já vale. É limite do formatador: ele **não
|
|
81
|
+
divide um nome pontuado** (`a.b.C`), e o parêntese que você pôs em volta
|
|
82
|
+
(`x = (\n a.b.C\n)`) some, porque não faz a linha caber. 🤷
|
|
83
|
+
|
|
84
|
+
#### O que o `fix` encurta sozinho
|
|
85
|
+
|
|
86
|
+
Dois desses casos têm uma única resposta certa, e o `fix` aplica:
|
|
87
|
+
|
|
88
|
+
- **comentário** é quebrado no limite; comentário no fim da linha sobe
|
|
89
|
+
para cima do código que ele comenta;
|
|
90
|
+
- **string** é partida nos espaços em pedaços concatenados entre
|
|
91
|
+
parênteses — o Python junta tudo de volta no mesmo valor.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# antes
|
|
95
|
+
text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb" # o texto
|
|
96
|
+
|
|
97
|
+
# depois do fix
|
|
98
|
+
# o texto
|
|
99
|
+
text = (
|
|
100
|
+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
|
|
101
|
+
"bbbbbbbbbbbbbbbb"
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
shortened 1 long line(s) ruff format cannot break (comments wrapped, strings split into concatenated parts):
|
|
107
|
+
mod.py:2 [comment+string]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
!!! check "Só entra o que é provado igual"
|
|
111
|
+
Antes de gravar, o `fix` compara a AST do arquivo antes e depois.
|
|
112
|
+
Comentário não entra na AST e o parser junta string concatenada numa
|
|
113
|
+
constante só, então uma reescrita certa sempre passa. Qualquer outra
|
|
114
|
+
coisa — um `\N{...}` cortado no meio, comentário empurrado para
|
|
115
|
+
dentro de uma continuação com `\` — muda a AST, e a linha fica como
|
|
116
|
+
estava.
|
|
117
|
+
|
|
118
|
+
Fica de fora, de propósito: comentário de ferramenta (`noqa`,
|
|
119
|
+
`type: ignore`, `fmt:`, `pragma`…), que precisa ficar na linha que
|
|
120
|
+
governa; f-string e string com três aspas; e palavra mais longa que uma
|
|
121
|
+
linha (URL, por exemplo).
|
|
122
|
+
|
|
123
|
+
#### O que sobra
|
|
124
|
+
|
|
125
|
+
Nome pontuado, nome longo e expressão não têm reescrita mecânica — e
|
|
126
|
+
quebrá-los à mão é desfeito pelo próximo `ruff format`. Para esses, o
|
|
127
|
+
`fix` explica cada linha logo abaixo do relatório do ruff:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
1 line(s) still too long after ruff format — the line-length setting was applied; the formatter has nowhere to break these:
|
|
131
|
+
controller.py:5 [dotted-name] `notifications.NOTIFICATION_USER_BODY_ALO_STATUS_CHANGE_WAITING`
|
|
132
|
+
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.
|
|
133
|
+
1 residual violation(s): 1 with no autofix (edit by hand).
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
O rótulo entre colchetes diz o que está no ponto onde a linha estoura:
|
|
137
|
+
|
|
138
|
+
| Rótulo | O que é | O que resolve |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `dotted-name` | nome pontuado (`modulo.CONSTANTE`) | alias, tabela/constante, indentação menor |
|
|
141
|
+
| `name` | um nome sozinho longo demais | renomear ou indentar menos |
|
|
142
|
+
| `string` | string que o `fix` não pôde partir (f-string, três aspas, palavra longa) | partir à mão em pedaços concatenados |
|
|
143
|
+
| `comment` | comentário que o `fix` não pôde quebrar (pragma, palavra longa) | quebrar à mão ou levar para a docstring |
|
|
144
|
+
| `expression` | qualquer outra coisa | extrair variável ou indentar menos |
|
|
145
|
+
|
|
146
|
+
A última linha separa o que **não tem autofix** do que o `fix --unsafe`
|
|
147
|
+
resolveria — sem ela, `Found 5 errors` não diz se vale rodar de novo.
|
|
148
|
+
|
|
149
|
+
!!! tip "Leve a escolha para uma tabela"
|
|
150
|
+
No exemplo acima, a indentação vem do `match`. Trocar o `match` por
|
|
151
|
+
um dicionário no nível do módulo tira quatro níveis e a linha cabe
|
|
152
|
+
exatamente em 79, sem 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 "E o `# noqa: E501`?"
|
|
168
|
+
Funciona, mas esconde o problema até o próximo refactor tirar o
|
|
169
|
+
`noqa` — que foi exatamente como esse caso apareceu. Prefira mudar a
|
|
170
|
+
forma do código.
|
|
171
|
+
|
|
51
172
|
## Um comando por vez
|
|
52
173
|
|
|
53
174
|
```bash
|
|
@@ -149,6 +270,9 @@ pytest.
|
|
|
149
270
|
- `check` é lint + fmt-check + type + test, nessa ordem, parando na
|
|
150
271
|
primeira falha.
|
|
151
272
|
- `fix` é a passada que conserta; `--unsafe` só quando você for revisar.
|
|
273
|
+
- Comentário e string longos o `fix` encurta sozinho, com a AST provada
|
|
274
|
+
igual. O E501 que sobra é linha sem reescrita mecânica: o `fix` diz o
|
|
275
|
+
token e a ação — mude a forma do código.
|
|
152
276
|
- `test --fast` / `check --fast` rodam a suíte com pytest-xdist
|
|
153
277
|
(`-n auto`, `-w N` para escolher); teste que só falha em paralelo se
|
|
154
278
|
confere rodando-o sozinho.
|