# flake8-trio
A highly opinionated flake8 plugin for [Trio](https://github.com/python-trio/trio)-related problems.
This can include anything from outright bugs, to pointless/dead code,
to likely performance issues, to minor points of idiom that might signal
a misunderstanding.
It may well be too noisy for anyone with different opinions, that's OK.
It also supports the [anyio](https://github.com/agronholm/anyio) library.
Pairs well with flake8-bugbear.
## Installation
```console
pip install flake8-trio
```
## List of warnings
- **TRIO100**: A `with trio.fail_after(...):` or `with trio.move_on_after(...):`
context does not contain any `await` statements. This makes it pointless, as
the timeout can only be triggered by a checkpoint.
- **TRIO101**: `yield` inside a nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
- **TRIO102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled` unless you use a shielded
cancel scope with a timeout.
- **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.
- **TRIO104**: `Cancelled` and `BaseException` must be re-raised - when a user tries to `return` or `raise` a different exception.
- **TRIO105**: Calling a trio async function without immediately `await`ing it.
- **TRIO106**: `trio` must be imported with `import trio` for the linter to work.
- **TRIO107**: Renamed to TRIO910
- **TRIO108**: Renamed to TRIO911
- **TRIO109**: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead
- **TRIO110**: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.
- **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.
- **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.
- **TRIO113**: Using `nursery.start_soon` in `__aenter__` doesn't wait for the task to begin. Consider replacing with `nursery.start`.
- **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.
- **TRIO115**: Replace `trio.sleep(0)` with the more suggestive `trio.lowlevel.checkpoint()`.
- **TRIO116**: `trio.sleep()` with >24 hour interval should usually be `trio.sleep_forever()`.
- **TRIO117**: Don't raise or catch `trio.[NonBase]MultiError`, prefer `[exceptiongroup.]BaseExceptionGroup`. Even if Trio still raises `MultiError` for legacy code, it can be caught with `BaseExceptionGroup` so it's fully redundant.
- **TRIO118**: Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable, since that breaks linter checks and multi-backend programs.
### Warnings for blocking sync calls in async functions
- **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.
- **TRIO210**: Sync HTTP call in async function, use `httpx.AsyncClient`.
- **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.
- **TRIO212**: Blocking sync HTTP call on httpx object, use httpx.AsyncClient.
- **TRIO220**: Sync process call in async function, use `await nursery.start(trio.run_process, ...)`.
- **TRIO221**: Sync process call in async function, use `await trio.run_process(...)`.
- **TRIO222**: Sync `os.*` call in async function, wrap in `await trio.to_thread.run_sync()`.
- **TRIO230**: Sync IO call in async function, use `trio.open_file(...)`.
- **TRIO231**: Sync IO call in async function, use `trio.wrap_file(...)`.
- **TRIO232**: Blocking sync call on file object, wrap the file object in `trio.wrap_file()` to get an async file object.
- **TRIO240**: Avoid using `os.path` in async functions, prefer using `trio.Path` objects.
### Warnings disabled by default
- **TRIO900**: Async generator without `@asynccontextmanager` not allowed.
- **TRIO910**: Exit or `return` from async function with no guaranteed checkpoint or exception since function definition.
- **TRIO911**: Exit, `yield` or `return` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
Checkpoints are `await`, `async for`, and `async with` (on one of enter/exit).
## Examples
### install and run through flake8
```sh
pip install flake8 flake8-trio
flake8 .
```
### install and run with pre-commit
If you use [pre-commit](https://pre-commit.com/), you can use it with flake8-trio by
adding the following to your `.pre-commit-config.yaml`:
```yaml
minimum_pre_commit_version: '2.9.0'
repos:
- repo: https://github.com/Zac-HD/flake8-trio
rev: 23.5.1
hooks:
- id: flake8-trio
# args: [--enable=TRIO, --disable=TRIO9, --autofix=TRIO]
```
This is often considerably faster for large projects, because `pre-commit`
can avoid running `flake8-trio` on unchanged files.
Afterwards, run
```sh
pip install pre-commit flake8-trio
pre-commit run .
```
### install and run as standalone
If inside a git repository, running without arguments will run it against all `*.py` files in the repository.
```sh
pip install flake8-trio
flake8-trio
```
#### with autofixes
```sh
flake8-trio --autofix=TRIO
```
#### specifying source files
```sh
flake8-trio my_python_file.py
```
##### zsh-only
```zsh
flake8-trio **/*.py
```
## Configuration
[You can configure `flake8` with command-line options](https://flake8.pycqa.org/en/latest/user/configuration.html),
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.
Note that it's not currently possible to use a configuration file when running `flake8-trio` standalone.
### `--enable`
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.
### `--disable`
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.
### `--autofix`
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.
### `--error-on-autofix`
Whether to also print an error message for autofixed errors.
### `--anyio`
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].
### `no-checkpoint-warning-decorators`
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`.
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.
For example:
```
no-checkpoint-warning-decorators =
mydecorator,
mydecoratorpackage.checkpointing_decorators.*,
ign*,
*.ignore,
```
### `startable-in-context-manager`
Comma-separated list of methods which should be used with `.start()` when opening a context manager,
in addition to the default `trio.run_process`, `trio.serve_tcp`, `trio.serve_ssl_over_tcp`, and
`trio.serve_listeners`. Names must be valid identifiers as per `str.isidentifier()`. For example:
```
startable-in-context-manager =
myfun,
myfun2,
```
### `trio200-blocking-calls`
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.
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.
Example:
```ini
trio200-blocking-calls =
my_blocking_call -> async.alternative,
module.block_call -> other_function_to_use,
common_error_call -> alternative(). But sometimes you should use other_function(). Ask joe if you're unsure which one,
dangerous_module.* -> corresponding function in safe_module,
*.dangerous_call -> .safe_call()
```
Specified patterns must not have parentheses, and will only match when the pattern is the name of a call, so given the above configuration
```python
async def my_function():
my_blocking_call() # this would raise an error
x = my_blocking_call(a, b, c) # as would this
y = my_blocking_call # but not this
y() # or this
[my_blocking_call][0]() # nor this
def my_blocking_call(): # it's also safe to use the name in other contexts
...
arbitrary_other_function(my_blocking_call=None)
```
# Changelog
*[CalVer, YY.month.patch](https://calver.org/)*
## 23.5.1
- TRIO91X now supports comprehensions
- TRIO100 and TRIO91X now supports autofixing
- Renamed `--enable-visitor-codes-regex` to `--enable`
- Added `--disable`, `--autofix` and `--error-on-autofix`
## 23.2.5
- Fix false alarms for `@pytest.fixture`-decorated functions in TRIO101, TRIO910 and TRIO911
## 23.2.4
- Fix TRIO900 false alarm on nested functions
- TRIO113 now also works on `anyio.TaskGroup`
## 23.2.3
- Fix get_matching_call when passed a single string as base. Resolves possibly several false alarms, TRIO210 among them.
## 23.2.2
- Rename TRIO107 to TRIO910, and TRIO108 to TRIO911, and making them optional by default.
- Allow `@pytest.fixture()`-decorated async generators, since they're morally context managers
- Add support for checking code written against [`anyio`](https://anyio.readthedocs.io/en/stable/)
- Add TRIO118: Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable, since that breaks linter checks and multi-backend programs.
## 23.2.1
- TRIO103 and TRIO104 no longer triggers when `trio.Cancelled` has been handled in previous except handlers.
- Add TRIO117: Reference to deprecated `trio.[NonBase]MultiError`; use `[Base]ExceptionGroup` instead.
- Add TRIO232: blocking sync call on file object.
- Add TRIO212: blocking sync call on `httpx.Client` object.
- Add TRIO222: blocking sync call to `os.wait*`
- TRIO221 now also looks for `os.posix_spawn[p]`
## 23.1.4
- TRIO114 avoids a false alarm on posonly args named "task_status"
- TRIO116 will now match on any attribute parameter named `.inf`, not just `math.inf`.
- TRIO900 now only checks `@asynccontextmanager`, not other decorators passed with --no-checkpoint-warning-decorators.
## 23.1.3
- Add TRIO240: usage of `os.path` in async function.
- Add TRIO900: ban async generators not decorated with known safe decorator
## 23.1.2
- Add TRIO230, TRIO231 - sync IO calls in async function
## 23.1.1
- Add TRIO210, TRIO211 - blocking sync call in async function, using network packages (requests, httpx, urllib3)
- Add TRIO220, TRIO221 - blocking sync call in async function, using subprocess or os.
## 22.12.5
- The `--startable-in-context-manager` and `--trio200-blocking-calls` options now handle spaces and newlines.
- Now compatible with [flake8-noqa](https://pypi.org/project/flake8-noqa/)'s NQA102 and NQA103 checks.
## 22.12.4
- TRIO200 no longer warns on directly awaited calls
## 22.12.3
- Worked around configuration-parsing bug for TRIO200 warning (more to come)
## 22.12.2
- Add TRIO200: User-configured blocking sync call in async function
## 22.12.1
- TRIO114 will now trigger on the unqualified name, will now only check the first parameter
directly, and parameters to function calls inside that.
- TRIO113 now only supports names that are valid identifiers, rather than fnmatch patterns.
- Add TRIO115: Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`.
## 22.11.5
- Add TRIO116: `trio.sleep()` with >24 hour interval should usually be `trio.sleep_forever()`.
## 22.11.4
- Add TRIO114 Startable function not in `--startable-in-context-manager` parameter list.
## 22.11.3
- Add TRIO113, prefer `await nursery.start(...)` to `nursery.start_soon()` for compatible functions when opening a context manager
## 22.11.2
- TRIO105 now also checks that you `await`ed `nursery.start()`.
## 22.11.1
- TRIO102 is no longer skipped in (async) context managers, since it's not a missing-checkpoint warning.
## 22.9.2
- Fix a crash on nontrivial decorator expressions (calls, PEP-614) and document behavior.
## 22.9.1
- Add `--no-checkpoint-warning-decorators` option, to disable missing-checkpoint warnings for certain decorated functions.
## 22.8.8
- Fix false alarm on TRIO107 with checkpointing `try` and empty `finally`
- Fix false alarm on TRIO107&108 with infinite loops
## 22.8.7
- TRIO107+108 now ignores `asynccontextmanager`s, since both `__aenter__` and `__aexit__` should checkpoint. `async with` is also treated as checkpointing on both enter and exit.
- TRIO107 now completely ignores any function whose body consists solely of ellipsis, pass, or string constants.
- TRIO103, 107 and 108 now inspects `while` conditions and `for` iterables to avoid false alarms on a couple cases where the loop body is guaranteed to run at least once.
## 22.8.6
- TRIO103 now correctly handles raises in loops, i.e. `raise` in else is guaranteed to run unless there's a `break` in the body.
## 22.8.5
- Add 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.
- Add TRIO112: this single-task nursery could be replaced by awaiting the function call directly.
## 22.8.4
- Fix TRIO108 raising errors on yields in some sync code.
- TRIO109 now skips all decorated functions to avoid false alarms
## 22.8.3
- TRIO108 now gives multiple error messages; one for each path lacking a guaranteed checkpoint
## 22.8.2
- Merged TRIO108 into TRIO107
- TRIO108 now handles checkpointing in async iterators
## 22.8.1
- Added TRIO109: Async definitions should not have a `timeout` parameter. Use `trio.[fail/move_on]_[at/after]`
- Added TRIO110: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.
## 22.7.6
- Extend TRIO102 to also check inside `except BaseException` and `except trio.Cancelled`
- Extend TRIO104 to also check for `yield`
- Update error messages on TRIO102 and TRIO103
## 22.7.5
- Add TRIO103: `except BaseException` or `except trio.Cancelled` with a code path that doesn't re-raise
- Add TRIO104: "Cancelled and BaseException must be re-raised" if user tries to return or raise a different exception.
- Added TRIO107: Async functions must have at least one checkpoint on every code path, unless an exception is raised
- Added TRIO108: Early return from async function must have at least one checkpoint on every code path before it.
## 22.7.4
- Added TRIO105 check for not immediately `await`ing async trio functions.
- Added TRIO106 check that trio is imported in a form that the plugin can easily parse.
## 22.7.3
- Added TRIO102 check for unsafe checkpoints inside `finally:` blocks
## 22.7.2
- Avoid `TRIO100` false-alarms on cancel scopes containing `async for` or `async with`.
## 22.7.1
- Initial release with TRIO100 and TRIO101