Skip to content

Commit 7748d40

Browse files
authored
Rename to flake8 async (#206)
* update docs, name of binary, and in general all changes possible without renaming files * rename error codes to ASYNCxxx, introducing temp functions to pretend test files are renamed. * rename Flake8TrioVisitor to Flake8AsyncVisitor
1 parent b00e77a commit 7748d40

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+689
-638
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: flake8-trio CI
2+
name: flake8-async CI
33

44
on:
55
push:

CONTRIBUTING.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@ Contributions welcome! We'll expand this guide as we go.
55

66
## Development
77

8-
When you wish to add a check to `flake8-trio` please ensure the following:
8+
When you wish to add a check to `flake8-async` please ensure the following:
99

1010
- `README.md` gets a one line about your new warning
1111
- Add a CHANGELOG entry (see 'Releasing a new version' below)
12-
- Unittests are added showing the check highlight where it should and shouldn't
13-
(see flake8-bugbear for examples of good linter tests)
14-
12+
- A test in `tests/eval_files` is added for your check. See the "Test generator" heading below.
1513
To run our test suite please use tox.
1614

1715
```console

README.md

+54-52
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,66 @@
11
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/python-trio/flake8-trio/main.svg)](https://results.pre-commit.ci/latest/github/python-trio/flake8-trio/main)
22
[![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
3-
# flake8-trio
3+
# flake8-async
44

5-
A highly opinionated flake8 plugin for [Trio](https://github.com/python-trio/trio)-related problems.
5+
A highly opinionated flake8 plugin for problems related to [Trio](https://github.com/python-trio/trio), [AnyIO](https://github.com/agronholm/anyio), or [asyncio](https://docs.python.org/3/library/asyncio.html).
66

77
This can include anything from outright bugs, to pointless/dead code,
88
to likely performance issues, to minor points of idiom that might signal
99
a misunderstanding.
1010

1111
It may well be too noisy for anyone with different opinions, that's OK.
1212

13-
It also supports the [anyio](https://github.com/agronholm/anyio) library.
14-
1513
Pairs well with flake8-bugbear.
1614

15+
Some checks are incorporated into [ruff](https://github.com/astral-sh/ruff).
16+
17+
This plugin was previously known as flake8-trio, and there was a separate small plugin known as flake8-async for asyncio. But this plugin was a superset of the checks in flake8-async, and support for anyio was added, so it's now named flake8-async to more properly convey its usage. At the same time all error codes were renamed from TRIOxxx to ASYNCxxx, as was previously used by the old flake8-async.
18+
1719
## Installation
1820

1921
```console
20-
pip install flake8-trio
22+
pip install flake8-async
2123
```
2224

2325
## List of warnings
2426

25-
- **TRIO100**: A `with trio.fail_after(...):` or `with trio.move_on_after(...):`
27+
- **ASYNC100**: A `with trio.fail_after(...):` or `with trio.move_on_after(...):`
2628
context does not contain any `await` statements. This makes it pointless, as
2729
the timeout can only be triggered by a checkpoint.
28-
- **TRIO101**: `yield` inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
29-
- **TRIO102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled` unless you use a shielded
30+
- **ASYNC101**: `yield` inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
31+
- **ASYNC102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled` unless you use a shielded
3032
cancel scope with a timeout.
31-
- **TRIO103**: `except BaseException`, `except trio.Cancelled` or a bare `except:` with a code path that doesn't re-raise. If you don't want to re-raise `BaseException`, add a separate handler for `trio.Cancelled` before.
32-
- **TRIO104**: `Cancelled` and `BaseException` must be re-raised - when a user tries to `return` or `raise` a different exception.
33-
- **TRIO105**: Calling a trio async function without immediately `await`ing it.
34-
- **TRIO106**: `trio` must be imported with `import trio` for the linter to work.
35-
- **TRIO109**: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead
36-
- **TRIO110**: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.
37-
- **TRIO111**: Variable, from context manager opened inside nursery, passed to `start[_soon]` might be invalidly accessed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager.
38-
- **TRIO112**: Nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call.
39-
- **TRIO113**: Using `nursery.start_soon` in `__aenter__` doesn't wait for the task to begin. Consider replacing with `nursery.start`.
40-
- **TRIO114**: Startable function (i.e. has a `task_status` keyword parameter) not in `--startable-in-context-manager` parameter list, please add it so TRIO113 can catch errors when using it.
41-
- **TRIO115**: Replace `trio.sleep(0)` with the more suggestive `trio.lowlevel.checkpoint()`.
42-
- **TRIO116**: `trio.sleep()` with >24 hour interval should usually be `trio.sleep_forever()`.
43-
- **TRIO118**: Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable, since that breaks linter checks and multi-backend programs.
33+
- **ASYNC103**: `except BaseException`, `except trio.Cancelled` or a bare `except:` with a code path that doesn't re-raise. If you don't want to re-raise `BaseException`, add a separate handler for `trio.Cancelled` before.
34+
- **ASYNC104**: `Cancelled` and `BaseException` must be re-raised - when a user tries to `return` or `raise` a different exception.
35+
- **ASYNC105**: Calling a trio async function without immediately `await`ing it.
36+
- **ASYNC106**: `trio`/`anyio` must be imported with `import trio`/`import anyio` for the linter to work.
37+
- **ASYNC109**: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead
38+
- **ASYNC110**: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.
39+
- **ASYNC111**: Variable, from context manager opened inside nursery, passed to `start[_soon]` might be invalidly accessed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager.
40+
- **ASYNC112**: Nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call.
41+
- **ASYNC113**: Using `nursery.start_soon` in `__aenter__` doesn't wait for the task to begin. Consider replacing with `nursery.start`.
42+
- **ASYNC114**: Startable function (i.e. has a `task_status` keyword parameter) not in `--startable-in-context-manager` parameter list, please add it so ASYNC113 can catch errors when using it.
43+
- **ASYNC115**: Replace `trio.sleep(0)` with the more suggestive `trio.lowlevel.checkpoint()`.
44+
- **ASYNC116**: `trio.sleep()` with >24 hour interval should usually be `trio.sleep_forever()`.
45+
- **ASYNC118**: Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable, since that breaks linter checks and multi-backend programs.
4446

4547
### Warnings for blocking sync calls in async functions
46-
- **TRIO200**: User-configured error for blocking sync calls in async functions. Does nothing by default, see [`trio200-blocking-calls`](#trio200-blocking-calls) for how to configure it.
47-
- **TRIO210**: Sync HTTP call in async function, use `httpx.AsyncClient`.
48-
- **TRIO211**: Likely sync HTTP call in async function, use `httpx.AsyncClient`. Looks for `urllib3` method calls on pool objects, but only matching on the method signature and not the object.
49-
- **TRIO212**: Blocking sync HTTP call on httpx object, use httpx.AsyncClient.
50-
- **TRIO220**: Sync process call in async function, use `await nursery.start(trio.run_process, ...)`.
51-
- **TRIO221**: Sync process call in async function, use `await trio.run_process(...)`.
52-
- **TRIO222**: Sync `os.*` call in async function, wrap in `await trio.to_thread.run_sync()`.
53-
- **TRIO230**: Sync IO call in async function, use `trio.open_file(...)`.
54-
- **TRIO231**: Sync IO call in async function, use `trio.wrap_file(...)`.
55-
- **TRIO232**: Blocking sync call on file object, wrap the file object in `trio.wrap_file()` to get an async file object.
56-
- **TRIO240**: Avoid using `os.path` in async functions, prefer using `trio.Path` objects.
48+
- **ASYNC200**: User-configured error for blocking sync calls in async functions. Does nothing by default, see [`trio200-blocking-calls`](#trio200-blocking-calls) for how to configure it.
49+
- **ASYNC210**: Sync HTTP call in async function, use `httpx.AsyncClient`.
50+
- **ASYNC211**: Likely sync HTTP call in async function, use `httpx.AsyncClient`. Looks for `urllib3` method calls on pool objects, but only matching on the method signature and not the object.
51+
- **ASYNC212**: Blocking sync HTTP call on httpx object, use httpx.AsyncClient.
52+
- **ASYNC220**: Sync process call in async function, use `await nursery.start(trio.run_process, ...)`.
53+
- **ASYNC221**: Sync process call in async function, use `await trio.run_process(...)`.
54+
- **ASYNC222**: Sync `os.*` call in async function, wrap in `await trio.to_thread.run_sync()`.
55+
- **ASYNC230**: Sync IO call in async function, use `trio.open_file(...)`.
56+
- **ASYNC231**: Sync IO call in async function, use `trio.wrap_file(...)`.
57+
- **ASYNC232**: Blocking sync call on file object, wrap the file object in `trio.wrap_file()` to get an async file object.
58+
- **ASYNC240**: Avoid using `os.path` in async functions, prefer using `trio.Path` objects.
5759

5860
### Warnings disabled by default
59-
- **TRIO900**: Async generator without `@asynccontextmanager` not allowed.
60-
- **TRIO910**: Exit or `return` from async function with no guaranteed checkpoint or exception since function definition.
61-
- **TRIO911**: Exit, `yield` or `return` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
61+
- **ASYNC900**: Async generator without `@asynccontextmanager` not allowed.
62+
- **ASYNC910**: Exit or `return` from async function with no guaranteed checkpoint or exception since function definition.
63+
- **ASYNC911**: Exit, `yield` or `return` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
6264
Checkpoints are `await`, `async for`, and `async with` (on one of enter/exit).
6365

6466
### Removed Warnings
@@ -69,55 +71,55 @@ pip install flake8-trio
6971
## Examples
7072
### install and run through flake8
7173
```sh
72-
pip install flake8 flake8-trio
74+
pip install flake8 flake8-async
7375
flake8 .
7476
```
7577
### install and run with pre-commit
76-
If you use [pre-commit](https://pre-commit.com/), you can use it with flake8-trio by
78+
If you use [pre-commit](https://pre-commit.com/), you can use it with flake8-async by
7779
adding the following to your `.pre-commit-config.yaml`:
7880

7981
```yaml
8082
minimum_pre_commit_version: '2.9.0'
8183
repos:
82-
- repo: https://github.com/Zac-HD/flake8-trio
84+
- repo: https://github.com/python-trio/flake8-async
8385
rev: 23.2.5
8486
hooks:
85-
- id: flake8-trio
86-
# args: [--enable=TRIO, --disable=TRIO9, --autofix=TRIO]
87+
- id: flake8-async
88+
# args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
8789
```
8890

8991
This is often considerably faster for large projects, because `pre-commit`
90-
can avoid running `flake8-trio` on unchanged files.
92+
can avoid running `flake8-async` on unchanged files.
9193

9294

9395
Afterwards, run
9496
```sh
95-
pip install pre-commit flake8-trio
97+
pip install pre-commit flake8-async
9698
pre-commit run .
9799
```
98100
### install and run as standalone
99101
If inside a git repository, running without arguments will run it against all `*.py` files in the repository.
100102
```sh
101-
pip install flake8-trio
102-
flake8-trio
103+
pip install flake8-async
104+
flake8-async
103105
```
104106
#### with autofixes
105107
```sh
106-
flake8-trio --autofix=TRIO
108+
flake8-async --autofix=ASYNC
107109
```
108110
#### specifying source files
109111
```sh
110-
flake8-trio my_python_file.py
112+
flake8-async my_python_file.py
111113
```
112114
##### zsh-only
113115
```zsh
114-
flake8-trio **/*.py
116+
flake8-async **/*.py
115117
```
116118

117119
## Configuration
118120
[You can configure `flake8` with command-line options](https://flake8.pycqa.org/en/latest/user/configuration.html),
119121
but we prefer using a config file. The file needs to start with a section marker `[flake8]` and the following options are then parsed using flake8's config parser, and can be used just like any other flake8 options.
120-
Note that it's not currently possible to use a configuration file when running `flake8-trio` standalone.
122+
Note that it's not currently possible to use a configuration file when running `flake8-async` standalone.
121123

122124
### `--enable`
123125
Comma-separated list of error codes to enable, similar to flake8 --select but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors.
@@ -126,7 +128,7 @@ Comma-separated list of error codes to enable, similar to flake8 --select but is
126128
Comma-separated list of error codes to disable, similar to flake8 --ignore but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors.
127129

128130
### `--autofix`
129-
Comma-separated list of error-codes to enable autofixing for if implemented. Requires running as a standalone program. Pass `--autofix=TRIO` to enable all autofixes.
131+
Comma-separated list of error-codes to enable autofixing for if implemented. Requires running as a standalone program. Pass `--autofix=ASYNC` to enable all autofixes.
130132

131133
### `--error-on-autofix`
132134
Whether to also print an error message for autofixed errors.
@@ -135,7 +137,7 @@ Whether to also print an error message for autofixed errors.
135137
Change the default library to be anyio instead of trio. If trio is imported it will assume both are available and print suggestions with [anyio|trio].
136138

137139
### `no-checkpoint-warning-decorators`
138-
Comma-separated list of decorators to disable checkpointing checks for, turning off TRIO910 and TRIO911 warnings for functions decorated with any decorator matching any in the list. Matching is done with [fnmatch](https://docs.python.org/3/library/fnmatch.html). Defaults to disabling for `asynccontextmanager`.
140+
Comma-separated list of decorators to disable checkpointing checks for, turning off ASYNC910 and ASYNC911 warnings for functions decorated with any decorator matching any in the list. Matching is done with [fnmatch](https://docs.python.org/3/library/fnmatch.html). Defaults to disabling for `asynccontextmanager`.
139141

140142
Decorators-to-match must be identifiers or dotted names only (not PEP-614 expressions), and will match against the name only - e.g. `foo.bar` matches `foo.bar`, `foo.bar()`, and `foo.bar(args, here)`, etc.
141143

@@ -159,14 +161,14 @@ startable-in-context-manager =
159161
myfun2,
160162
```
161163

162-
### `trio200-blocking-calls`
164+
### `async200-blocking-calls`
163165
Comma-separated list of pairs of values separated by `->` (optional whitespace stripped), where the first is a pattern for a call that should raise an error if found inside an async function, and the second is what should be suggested to use instead. It uses fnmatch as per [`no-checkpoint-warning-decorators`](#no-checkpoint-warning-decorators) for matching. The part after `->` is not used by the checker other than when printing the error, so you could add extra info there if you want.
164166

165167
The format of the error message is `User-configured blocking sync call {0} in async function, consider replacing with {1}.`, where `{0}` is the pattern the call matches and `{1}` is the suggested replacement.
166168

167169
Example:
168170
```ini
169-
trio200-blocking-calls =
171+
async200-blocking-calls =
170172
my_blocking_call -> async.alternative,
171173
module.block_call -> other_function_to_use,
172174
common_error_call -> alternative(). But sometimes you should use other_function(). Ask joe if you're unsure which one,

flake8_trio/__init__.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
import libcst as cst
2525

26-
from .base import Options
26+
from .base import Options, error_has_subidentifier
2727
from .runner import Flake8TrioRunner, Flake8TrioRunner_cst
2828
from .visitors import ERROR_CLASSES, ERROR_CLASSES_CST, default_disabled_error_codes
2929

@@ -199,7 +199,7 @@ def add_options(option_manager: OptionManager | ArgumentParser):
199199
)
200200
else: # if run as a flake8 plugin
201201
Plugin.standalone = False
202-
# Disable TRIO9xx calls by default
202+
# Disable ASYNC9xx calls by default
203203
option_manager.extend_default_ignore(default_disabled_error_codes)
204204
# add parameter to parse from flake8 config
205205
add_argument = functools.partial( # type: ignore
@@ -209,7 +209,7 @@ def add_options(option_manager: OptionManager | ArgumentParser):
209209
add_argument(
210210
"--enable",
211211
type=comma_separated_list,
212-
default="TRIO",
212+
default="ASYNC",
213213
required=False,
214214
help=(
215215
"Comma-separated list of error codes to enable, similar to flake8"
@@ -221,7 +221,7 @@ def add_options(option_manager: OptionManager | ArgumentParser):
221221
add_argument(
222222
"--disable",
223223
type=comma_separated_list,
224-
default="TRIO9" if Plugin.standalone else "",
224+
default="ASYNC9" if Plugin.standalone else "",
225225
required=False,
226226
help=(
227227
"Comma-separated list of error codes to disable, similar to flake8"
@@ -253,7 +253,7 @@ def add_options(option_manager: OptionManager | ArgumentParser):
253253
required=False,
254254
type=comma_separated_list,
255255
help=(
256-
"Comma-separated list of decorators to disable TRIO910 & TRIO911 "
256+
"Comma-separated list of decorators to disable ASYNC910 & ASYNC911 "
257257
"checkpoint warnings for. "
258258
"Decorators can be dotted or not, as well as support * as a wildcard. "
259259
"For example, ``--no-checkpoint-warning-decorators=app.route,"
@@ -266,7 +266,7 @@ def add_options(option_manager: OptionManager | ArgumentParser):
266266
default="",
267267
required=False,
268268
help=(
269-
"Comma-separated list of method calls to additionally enable TRIO113 "
269+
"Comma-separated list of method calls to additionally enable ASYNC113 "
270270
"warnings for. Will also check for the pattern inside function calls. "
271271
"Methods must be valid identifiers as per `str.isidientifier()` and "
272272
"not reserved keywords. "
@@ -281,7 +281,7 @@ def add_options(option_manager: OptionManager | ArgumentParser):
281281
required=False,
282282
help=(
283283
"Comma-separated list of key->value pairs, where key is a [dotted] "
284-
"function that if found inside an async function will raise TRIO200, "
284+
"function that if found inside an async function will raise ASYNC200, "
285285
"suggesting it be replaced with {value}"
286286
),
287287
)
@@ -313,8 +313,9 @@ def get_matching_codes(
313313
err_code
314314
for err_class in (*ERROR_CLASSES, *ERROR_CLASSES_CST)
315315
for err_code in err_class.error_codes # type: ignore[attr-defined]
316-
if len(err_code) == 7 # exclude e.g. TRIO103_anyio_trio
316+
if not error_has_subidentifier(err_code) # exclude e.g. ASYNC103_anyio_trio
317317
}
318+
assert all_codes
318319

319320
if options.autofix and not Plugin.standalone:
320321
print("Cannot autofix when run as a flake8 plugin.", file=sys.stderr)
@@ -328,8 +329,10 @@ def get_matching_codes(
328329
enabled_codes -= set(get_matching_codes(options.disable, enabled_codes))
329330

330331
# if disable has default value, re-enable explicitly enabled codes
331-
if options.disable == ["TRIO9"]:
332-
enabled_codes.update(code for code in options.enable if len(code) == 7)
332+
if options.disable == ["ASYNC9"]:
333+
enabled_codes.update(
334+
code for code in options.enable if not error_has_subidentifier(code)
335+
)
333336

334337
Plugin._options = Options(
335338
enabled_codes=enabled_codes,

flake8_trio/base.py

+11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@
99
from collections.abc import Collection
1010

1111

12+
# strip the sub-identifier on error used to specify which message to print, when
13+
# several different ones are used for the same code. Used in e.g. Visitor103
14+
def strip_error_subidentifier(s: str) -> str:
15+
"""Turn e.g. ASYNC103_anyio_trio => ASYNC103."""
16+
return s.split("_")[0]
17+
18+
19+
def error_has_subidentifier(s: str) -> bool:
20+
return "_" in s
21+
22+
1223
@dataclass
1324
class Options:
1425
# error codes to give errors for

0 commit comments

Comments
 (0)