flake8-async 24.4.2__tar.gz → 24.5.2__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.
- {flake8_async-24.4.2 → flake8_async-24.5.2}/CHANGELOG.md +9 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/CONTRIBUTING.md +9 -5
- {flake8_async-24.4.2 → flake8_async-24.5.2}/PKG-INFO +12 -2
- {flake8_async-24.4.2 → flake8_async-24.5.2}/README.md +2 -1
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/__init__.py +1 -1
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/__init__.py +0 -1
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/flake8asyncvisitor.py +11 -3
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/helpers.py +68 -18
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor101.py +12 -2
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor111.py +12 -5
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor91x.py +99 -15
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitors.py +18 -6
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async.egg-info/PKG-INFO +12 -2
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async.egg-info/SOURCES.txt +10 -1
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/autofix_files/async100.py +4 -3
- flake8_async-24.5.2/tests/autofix_files/async100_asyncio.py +22 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/autofix_files/async910.py +1 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/autofix_files/async911.py +1 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/autofix_files/async91x_autofix.py +20 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/autofix_files/noqa_testing.py +3 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async100.py +4 -3
- flake8_async-24.5.2/tests/eval_files/async100_asyncio.py +22 -0
- flake8_async-24.5.2/tests/eval_files/async101.py +129 -0
- flake8_async-24.5.2/tests/eval_files/async101_anyio.py +10 -0
- flake8_async-24.5.2/tests/eval_files/async101_asyncio.py +82 -0
- flake8_async-24.5.2/tests/eval_files/async101_trio.py +17 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async111.py +9 -1
- flake8_async-24.5.2/tests/eval_files/async111_anyio.py +21 -0
- flake8_async-24.5.2/tests/eval_files/async111_asyncio.py +23 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async112.py +4 -4
- flake8_async-24.5.2/tests/eval_files/async112_anyio.py +28 -0
- flake8_async-24.5.2/tests/eval_files/async112_asyncio.py +24 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async113_trio.py +12 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async910.py +1 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async911.py +1 -0
- flake8_async-24.5.2/tests/eval_files/async912.py +184 -0
- flake8_async-24.5.2/tests/eval_files/async912_asyncio.py +76 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async91x_autofix.py +19 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/noqa_testing.py +3 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_flake8_async.py +38 -13
- flake8_async-24.4.2/flake8_async/visitors/visitor100.py +0 -90
- flake8_async-24.4.2/tests/eval_files/async100_asyncio.py +0 -23
- flake8_async-24.4.2/tests/eval_files/async101.py +0 -74
- {flake8_async-24.4.2 → flake8_async-24.5.2}/LICENSE +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/MANIFEST.in +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/__main__.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/base.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/runner.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor102.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor103_104.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor105.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor118.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor2xx.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async/visitors/visitor_utility.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async.egg-info/dependency_links.txt +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async.egg-info/entry_points.txt +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async.egg-info/not-zip-safe +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async.egg-info/requires.txt +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/flake8_async.egg-info/top_level.txt +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/pyproject.toml +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/setup.cfg +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/setup.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/__init__.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/autofix_files/async100_simple_autofix.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/autofix_files/noqa.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/conftest.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/anyio_trio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async100_noautofix.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async100_simple_autofix.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async102.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async102_aclose.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async102_aclose_args.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async102_anyio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async102_asyncio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async102_trio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async103.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async103_all_imported.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async103_both_imported.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async103_no_104.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async103_trio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async104.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async104_anyio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async104_trio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async105.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async105_anyio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async106.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async109.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async110.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async113.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async113_anyio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async114.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async115.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async116.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async118.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async119.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async200.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async210.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async211.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async212.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async22x.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async22x_asyncio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async232.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async232_asyncio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async23x.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async23x_asyncio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async240.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async250.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async250_multi_library.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async251.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async251_multi_library.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async900.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/async91x_noautofix.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/no_library.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/noqa.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/noqa_no_autofix.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/eval_files/trio_anyio.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_all_visitors_imported.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_changelog_and_version.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_config_and_args.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_decorator.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_exception_on_invalid_code.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_formatting.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/test_messages_documented.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tests/trio_options.py +0 -0
- {flake8_async-24.4.2 → flake8_async-24.5.2}/tox.ini +0 -0
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
*[CalVer, YY.month.patch](https://calver.org/)*
|
|
3
3
|
|
|
4
|
+
## 24.5.2
|
|
5
|
+
- ASYNC101 now also warns on anyio & asyncio taskgroup.s
|
|
6
|
+
- Fixed a bug where ASYNC101 and ASYNC91x would not recognize decorators with parameters directly imported. I.e. `@fixture(...)` will now suppress errors.
|
|
7
|
+
|
|
8
|
+
## 24.5.1
|
|
9
|
+
- Add ASYNC912: no checkpoints in with statement are guaranteed to run.
|
|
10
|
+
- ASYNC100 now properly treats async for comprehensions as checkpoints.
|
|
11
|
+
- ASYNC100 now supports autofixing on asyncio.
|
|
12
|
+
|
|
4
13
|
## 24.4.2
|
|
5
14
|
- Add ASYNC119: yield in contextmanager in async generator.
|
|
6
15
|
|
|
@@ -44,11 +44,15 @@ You can instead of `error` specify the error code.
|
|
|
44
44
|
With `# ARG` lines you can also specify command-line arguments that should be passed to the plugin when parsing a file. Can be specified multiple times for several different arguments.
|
|
45
45
|
Generated tests will by default `--select` the error code of the file, which will enable any visitors that can generate that code (and if those visitors can raise other codes they might be raised too). This can be overridden by adding an `# ARG --select=...` line.
|
|
46
46
|
|
|
47
|
-
###
|
|
48
|
-
Eval files are
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
### Library parametrization
|
|
48
|
+
Eval files are evaluated with each supported library. It does this by replacing all instances of the `BASE_LIBRARY` ("trio" by default) with the two other libraries, and setting the corresponding flag (`--anyio` or `--asyncio`).
|
|
49
|
+
### `# BASE_LIBRARY anyio` / `# BASE_LIBRARY asyncio`
|
|
50
|
+
Defaults to `trio`. Used to specify the primary library an eval file is testing.
|
|
51
|
+
|
|
52
|
+
#### `# ANYIO_NO_ERROR`, `# TRIO_NO_ERROR`, `# ASYNCIO_NO_ERROR`
|
|
53
|
+
A file which is marked with this will ignore all `# error` or `# TRIO...` comments when running with anyio. Use when an error is library-specific and replacing all instances means the file should no longer raise any errors.
|
|
54
|
+
### `# NOANYIO`, `# NOTRIO`, `#NOASYNCIO`
|
|
55
|
+
Disables checking a file with the specified library. Should be used somewhat sparingly, and always have a comment motivating its use.
|
|
52
56
|
|
|
53
57
|
## Running pytest outside tox
|
|
54
58
|
If you don't want to bother with tox to quickly test stuff, you'll need to install the following dependencies:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flake8-async
|
|
3
|
-
Version: 24.
|
|
3
|
+
Version: 24.5.2
|
|
4
4
|
Summary: A highly opinionated flake8 plugin for Trio-related problems.
|
|
5
5
|
Home-page: https://github.com/python-trio/flake8-async
|
|
6
6
|
Author: Zac Hatfield-Dodds, John Litborn, and Contributors
|
|
@@ -89,6 +89,7 @@ Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
|
|
|
89
89
|
- **ASYNC910**: Exit or `return` from async function with no guaranteed checkpoint or exception since function definition. You might want to enable this on a codebase to make it easier to reason about checkpoints, and make the logic of ASYNC911 correct.
|
|
90
90
|
- **ASYNC911**: Exit, `yield` or `return` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
|
|
91
91
|
Checkpoints are `await`, `async for`, and `async with` (on one of enter/exit).
|
|
92
|
+
- **ASYNC912**: Timeout/Cancelscope has no awaits that are guaranteed to run. If the scope has no checkpoints at all, then `ASYNC100` will be raised instead.
|
|
92
93
|
|
|
93
94
|
### Removed Warnings
|
|
94
95
|
- **TRIOxxx**: All error codes are now renamed ASYNCxxx
|
|
@@ -110,7 +111,7 @@ adding the following to your `.pre-commit-config.yaml`:
|
|
|
110
111
|
minimum_pre_commit_version: '2.9.0'
|
|
111
112
|
repos:
|
|
112
113
|
- repo: https://github.com/python-trio/flake8-async
|
|
113
|
-
rev: 24.
|
|
114
|
+
rev: 24.5.2
|
|
114
115
|
hooks:
|
|
115
116
|
- id: flake8-async
|
|
116
117
|
# args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
|
|
@@ -225,6 +226,15 @@ async def my_function():
|
|
|
225
226
|
# Changelog
|
|
226
227
|
*[CalVer, YY.month.patch](https://calver.org/)*
|
|
227
228
|
|
|
229
|
+
## 24.5.2
|
|
230
|
+
- ASYNC101 now also warns on anyio & asyncio taskgroup.s
|
|
231
|
+
- Fixed a bug where ASYNC101 and ASYNC91x would not recognize decorators with parameters directly imported. I.e. `@fixture(...)` will now suppress errors.
|
|
232
|
+
|
|
233
|
+
## 24.5.1
|
|
234
|
+
- Add ASYNC912: no checkpoints in with statement are guaranteed to run.
|
|
235
|
+
- ASYNC100 now properly treats async for comprehensions as checkpoints.
|
|
236
|
+
- ASYNC100 now supports autofixing on asyncio.
|
|
237
|
+
|
|
228
238
|
## 24.4.2
|
|
229
239
|
- Add ASYNC119: yield in contextmanager in async generator.
|
|
230
240
|
|
|
@@ -64,6 +64,7 @@ Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
|
|
|
64
64
|
- **ASYNC910**: Exit or `return` from async function with no guaranteed checkpoint or exception since function definition. You might want to enable this on a codebase to make it easier to reason about checkpoints, and make the logic of ASYNC911 correct.
|
|
65
65
|
- **ASYNC911**: Exit, `yield` or `return` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
|
|
66
66
|
Checkpoints are `await`, `async for`, and `async with` (on one of enter/exit).
|
|
67
|
+
- **ASYNC912**: Timeout/Cancelscope has no awaits that are guaranteed to run. If the scope has no checkpoints at all, then `ASYNC100` will be raised instead.
|
|
67
68
|
|
|
68
69
|
### Removed Warnings
|
|
69
70
|
- **TRIOxxx**: All error codes are now renamed ASYNCxxx
|
|
@@ -85,7 +86,7 @@ adding the following to your `.pre-commit-config.yaml`:
|
|
|
85
86
|
minimum_pre_commit_version: '2.9.0'
|
|
86
87
|
repos:
|
|
87
88
|
- repo: https://github.com/python-trio/flake8-async
|
|
88
|
-
rev: 24.
|
|
89
|
+
rev: 24.5.2
|
|
89
90
|
hooks:
|
|
90
91
|
- id: flake8-async
|
|
91
92
|
# args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
|
|
@@ -98,7 +98,11 @@ class Flake8AsyncVisitor(ast.NodeVisitor, ABC):
|
|
|
98
98
|
), "No error code defined, but class has multiple codes"
|
|
99
99
|
error_code = next(iter(self.error_codes))
|
|
100
100
|
# don't emit an error if this code is disabled in a multi-code visitor
|
|
101
|
-
elif
|
|
101
|
+
elif (
|
|
102
|
+
(ec_no_sub := strip_error_subidentifier(error_code))
|
|
103
|
+
not in self.options.enabled_codes
|
|
104
|
+
and ec_no_sub not in self.options.autofix_codes
|
|
105
|
+
):
|
|
102
106
|
return
|
|
103
107
|
|
|
104
108
|
self.__state.problems.append(
|
|
@@ -217,7 +221,11 @@ class Flake8AsyncVisitor_cst(cst.CSTTransformer, ABC):
|
|
|
217
221
|
error_code = next(iter(self.error_codes))
|
|
218
222
|
# don't emit an error if this code is disabled in a multi-code visitor
|
|
219
223
|
# TODO: write test for only one of 910/911 enabled/autofixed
|
|
220
|
-
elif
|
|
224
|
+
elif (
|
|
225
|
+
(ec_no_sub := strip_error_subidentifier(error_code))
|
|
226
|
+
not in self.options.enabled_codes
|
|
227
|
+
and ec_no_sub not in self.options.autofix_codes
|
|
228
|
+
):
|
|
221
229
|
return False # pragma: no cover
|
|
222
230
|
|
|
223
231
|
if self.is_noqa(node, error_code):
|
|
@@ -237,7 +245,7 @@ class Flake8AsyncVisitor_cst(cst.CSTTransformer, ABC):
|
|
|
237
245
|
return True
|
|
238
246
|
|
|
239
247
|
def should_autofix(self, node: cst.CSTNode, code: str | None = None) -> bool:
|
|
240
|
-
if code is None:
|
|
248
|
+
if code is None: # pragma: no cover
|
|
241
249
|
assert len(self.error_codes) == 1
|
|
242
250
|
code = next(iter(self.error_codes))
|
|
243
251
|
# this does not currently need to check for `noqa`s, as error() does that
|
|
@@ -51,6 +51,7 @@ def error_class_cst(error_class: type[T_CST]) -> type[T_CST]:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def disabled_by_default(error_class: type[T_EITHER]) -> type[T_EITHER]:
|
|
54
|
+
"""Default-disables all error codes in a class."""
|
|
54
55
|
assert error_class.error_codes # type: ignore[attr-defined]
|
|
55
56
|
default_disabled_error_codes.extend(
|
|
56
57
|
error_class.error_codes # type: ignore[attr-defined]
|
|
@@ -58,6 +59,11 @@ def disabled_by_default(error_class: type[T_EITHER]) -> type[T_EITHER]:
|
|
|
58
59
|
return error_class
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
def disable_codes_by_default(*codes: str) -> None:
|
|
63
|
+
"""Default-disables only specified codes."""
|
|
64
|
+
default_disabled_error_codes.extend(codes)
|
|
65
|
+
|
|
66
|
+
|
|
61
67
|
def utility_visitor(c: type[T]) -> type[T]:
|
|
62
68
|
assert not hasattr(c, "error_codes")
|
|
63
69
|
c.error_codes = {}
|
|
@@ -83,7 +89,8 @@ def _get_identifier(node: ast.expr) -> str:
|
|
|
83
89
|
|
|
84
90
|
|
|
85
91
|
# ignores module and only checks the unqualified name of the decorator
|
|
86
|
-
# used in 101,
|
|
92
|
+
# used in 113. cst version used in 101, 900 and 910/911
|
|
93
|
+
# matches @name, @foo.name, @name(...), and @foo.name(...)
|
|
87
94
|
def has_decorator(node: ast.FunctionDef | ast.AsyncFunctionDef, *names: str):
|
|
88
95
|
return any(_get_identifier(dec) in names for dec in node.decorator_list)
|
|
89
96
|
|
|
@@ -317,30 +324,68 @@ class AttributeCall(NamedTuple):
|
|
|
317
324
|
function: str
|
|
318
325
|
|
|
319
326
|
|
|
327
|
+
# the custom __or__ in libcst breaks pyright type checking. It's possible to use
|
|
328
|
+
# `Union` as a workaround ... except pyupgrade will automatically replace that.
|
|
329
|
+
# So we have to resort to specifying one of the base classes.
|
|
330
|
+
# See https://github.com/Instagram/LibCST/issues/1143
|
|
331
|
+
def build_cst_matcher(attr: str) -> m.BaseExpression:
|
|
332
|
+
"""Build a cst matcher structure with attributes&names matching a string `a.b.c`."""
|
|
333
|
+
if "." not in attr:
|
|
334
|
+
return m.Name(value=attr)
|
|
335
|
+
body, tail = attr.rsplit(".")
|
|
336
|
+
return m.Attribute(value=build_cst_matcher(body), attr=m.Name(value=tail))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def identifier_to_string(attr: cst.Name | cst.Attribute) -> str:
|
|
340
|
+
if isinstance(attr, cst.Name):
|
|
341
|
+
return attr.value
|
|
342
|
+
assert isinstance(attr.value, (cst.Attribute, cst.Name))
|
|
343
|
+
return identifier_to_string(attr.value) + "." + attr.attr.value
|
|
344
|
+
|
|
345
|
+
|
|
320
346
|
def with_has_call(
|
|
321
|
-
node: cst.With, *names: str, base: Iterable[str] = ("trio", "anyio")
|
|
347
|
+
node: cst.With, *names: str, base: Iterable[str] | str = ("trio", "anyio")
|
|
322
348
|
) -> list[AttributeCall]:
|
|
349
|
+
"""Check if a with statement has a matching call, returning a list with matches.
|
|
350
|
+
|
|
351
|
+
`names` specify the names of functions to match, `base` specifies the
|
|
352
|
+
library/module(s) the function must be in.
|
|
353
|
+
The list elements in the return value are named tuples with the matched node,
|
|
354
|
+
base and function.
|
|
355
|
+
|
|
356
|
+
Examples_
|
|
357
|
+
|
|
358
|
+
`with_has_call(node, "bar", base="foo")` matches foo.bar.
|
|
359
|
+
`with_has_call(node, "bar", "bee", base=("foo", "a.b.c")` matches
|
|
360
|
+
`foo.bar`, `foo.bee`, `a.b.c.bar`, and `a.b.c.bee`.
|
|
361
|
+
|
|
362
|
+
"""
|
|
363
|
+
if isinstance(base, str):
|
|
364
|
+
base = (base,) # pragma: no cover
|
|
365
|
+
|
|
366
|
+
# build matcher, using SaveMatchedNode to save the base and the function name.
|
|
367
|
+
matcher = m.Call(
|
|
368
|
+
func=m.Attribute(
|
|
369
|
+
value=m.SaveMatchedNode(
|
|
370
|
+
m.OneOf(*(build_cst_matcher(b) for b in base)), name="base"
|
|
371
|
+
),
|
|
372
|
+
attr=m.SaveMatchedNode(
|
|
373
|
+
oneof_names(*names),
|
|
374
|
+
name="function",
|
|
375
|
+
),
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
|
|
323
379
|
res_list: list[AttributeCall] = []
|
|
324
380
|
for item in node.items:
|
|
325
|
-
if res := m.extract(
|
|
326
|
-
item.item,
|
|
327
|
-
m.Call(
|
|
328
|
-
func=m.Attribute(
|
|
329
|
-
value=m.SaveMatchedNode(m.Name(), name="library"),
|
|
330
|
-
attr=m.SaveMatchedNode(
|
|
331
|
-
oneof_names(*names),
|
|
332
|
-
name="function",
|
|
333
|
-
),
|
|
334
|
-
)
|
|
335
|
-
),
|
|
336
|
-
):
|
|
381
|
+
if res := m.extract(item.item, matcher):
|
|
337
382
|
assert isinstance(item.item, cst.Call)
|
|
338
|
-
assert isinstance(res["
|
|
383
|
+
assert isinstance(res["base"], (cst.Name, cst.Attribute))
|
|
339
384
|
assert isinstance(res["function"], cst.Name)
|
|
340
|
-
if res["library"].value not in base:
|
|
341
|
-
continue
|
|
342
385
|
res_list.append(
|
|
343
|
-
AttributeCall(
|
|
386
|
+
AttributeCall(
|
|
387
|
+
item.item, identifier_to_string(res["base"]), res["function"].value
|
|
388
|
+
)
|
|
344
389
|
)
|
|
345
390
|
return res_list
|
|
346
391
|
|
|
@@ -351,8 +396,13 @@ def func_has_decorator(func: cst.FunctionDef, *names: str) -> bool:
|
|
|
351
396
|
func.decorators,
|
|
352
397
|
m.Decorator(
|
|
353
398
|
decorator=m.OneOf(
|
|
399
|
+
# @name
|
|
354
400
|
oneof_names(*names),
|
|
401
|
+
# @foo.name
|
|
355
402
|
m.Attribute(attr=oneof_names(*names)),
|
|
403
|
+
# @name(...)
|
|
404
|
+
m.Call(func=oneof_names(*names)),
|
|
405
|
+
# @foo.name(...)
|
|
356
406
|
m.Call(func=m.Attribute(attr=oneof_names(*names))),
|
|
357
407
|
)
|
|
358
408
|
),
|
|
@@ -40,12 +40,22 @@ class Visitor101(Flake8AsyncVisitor_cst):
|
|
|
40
40
|
self.save_state(node, "_yield_is_error", copy=True)
|
|
41
41
|
# if there's no safe decorator,
|
|
42
42
|
# and it's not yet been determined that yield is error
|
|
43
|
-
# and this withitem opens a cancelscope:
|
|
43
|
+
# and this withitem opens a nursery/taskgroup/cancelscope:
|
|
44
44
|
# then yielding is unsafe
|
|
45
45
|
self._yield_is_error = (
|
|
46
46
|
not self._safe_decorator
|
|
47
47
|
and not self._yield_is_error
|
|
48
|
-
|
|
48
|
+
# It's not strictly necessary to specify the base, as raising errors on
|
|
49
|
+
# e.g. anyio.open_nursery isn't much of a problem.
|
|
50
|
+
and bool(
|
|
51
|
+
# nursery/taskgroup
|
|
52
|
+
with_has_call(node, "open_nursery", base="trio")
|
|
53
|
+
or with_has_call(node, "create_task_group", base="anyio")
|
|
54
|
+
or with_has_call(node, "TaskGroup", base="asyncio")
|
|
55
|
+
# cancel scopes
|
|
56
|
+
or with_has_call(node, "timeout", "timeout_at", base="asyncio")
|
|
57
|
+
or with_has_call(node, *cancel_scope_names, base=("trio", "anyio"))
|
|
58
|
+
)
|
|
49
59
|
)
|
|
50
60
|
|
|
51
61
|
def leave_With(
|
|
@@ -12,13 +12,21 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from collections.abc import Mapping
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def is_nursery_like(node: ast.expr) -> bool:
|
|
16
|
+
return bool(
|
|
17
|
+
get_matching_call(node, "open_nursery", base="trio")
|
|
18
|
+
or get_matching_call(node, "create_task_group", base="anyio")
|
|
19
|
+
or get_matching_call(node, "TaskGroup", base="asyncio")
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
15
23
|
@error_class
|
|
16
24
|
class Visitor111(Flake8AsyncVisitor):
|
|
17
25
|
error_codes: Mapping[str, str] = {
|
|
18
26
|
"ASYNC111": (
|
|
19
27
|
"variable {2} is usable within the context manager on line {0}, but that "
|
|
20
|
-
"will close before nursery opened on line {1} - this is usually
|
|
21
|
-
"
|
|
28
|
+
"will close before nursery/taskgroup opened on line {1} - this is usually "
|
|
29
|
+
"a bug. Nursery/TaskGroup should generally be the inner-most context manager."
|
|
22
30
|
),
|
|
23
31
|
}
|
|
24
32
|
|
|
@@ -48,8 +56,7 @@ class Visitor111(Flake8AsyncVisitor):
|
|
|
48
56
|
self.TrioContextManager(
|
|
49
57
|
item.context_expr.lineno,
|
|
50
58
|
item.optional_vars.id,
|
|
51
|
-
|
|
52
|
-
is not None,
|
|
59
|
+
is_nursery_like(item.context_expr),
|
|
53
60
|
)
|
|
54
61
|
)
|
|
55
62
|
|
|
@@ -75,7 +82,7 @@ class Visitor111(Flake8AsyncVisitor):
|
|
|
75
82
|
if (
|
|
76
83
|
isinstance(node.func, ast.Attribute)
|
|
77
84
|
and isinstance(node.func.value, ast.Name)
|
|
78
|
-
and node.func.attr in ("start", "start_soon")
|
|
85
|
+
and node.func.attr in ("start", "start_soon", "create_task")
|
|
79
86
|
):
|
|
80
87
|
self._nursery_call = None
|
|
81
88
|
for i, cm in enumerate(self._context_managers):
|
|
@@ -18,19 +18,32 @@ from libcst.metadata import PositionProvider
|
|
|
18
18
|
from ..base import Statement
|
|
19
19
|
from .flake8asyncvisitor import Flake8AsyncVisitor_cst
|
|
20
20
|
from .helpers import (
|
|
21
|
-
|
|
21
|
+
AttributeCall,
|
|
22
|
+
cancel_scope_names,
|
|
23
|
+
disable_codes_by_default,
|
|
22
24
|
error_class_cst,
|
|
25
|
+
flatten_preserving_comments,
|
|
23
26
|
fnmatch_qualified_name_cst,
|
|
24
27
|
func_has_decorator,
|
|
25
28
|
iter_guaranteed_once_cst,
|
|
29
|
+
with_has_call,
|
|
26
30
|
)
|
|
27
31
|
|
|
28
32
|
if TYPE_CHECKING:
|
|
29
33
|
from collections.abc import Mapping, Sequence
|
|
30
34
|
|
|
31
35
|
|
|
36
|
+
class ArtificialStatement(Statement):
|
|
37
|
+
"""Statement that should not trigger 910/911 on function exit.
|
|
38
|
+
|
|
39
|
+
Used by loops and `with` statements.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
32
43
|
# Statement injected at the start of loops to track missed checkpoints.
|
|
33
|
-
ARTIFICIAL_STATEMENT =
|
|
44
|
+
ARTIFICIAL_STATEMENT = ArtificialStatement("artificial", -1)
|
|
45
|
+
# There's no particular reason why loops use a globally instanced statement, but
|
|
46
|
+
# `with` does not - mostly just an artifact of them being implemented at different times.
|
|
34
47
|
|
|
35
48
|
|
|
36
49
|
def func_empty_body(node: cst.FunctionDef) -> bool:
|
|
@@ -231,8 +244,10 @@ class InsertCheckpointsInLoopBody(CommonVisitors):
|
|
|
231
244
|
leave_Return = leave_Yield # type: ignore
|
|
232
245
|
|
|
233
246
|
|
|
247
|
+
disable_codes_by_default("ASYNC910", "ASYNC911", "ASYNC912")
|
|
248
|
+
|
|
249
|
+
|
|
234
250
|
@error_class_cst
|
|
235
|
-
@disabled_by_default
|
|
236
251
|
class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
237
252
|
error_codes: Mapping[str, str] = {
|
|
238
253
|
"ASYNC910": (
|
|
@@ -243,6 +258,14 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
243
258
|
"{0} from async iterable with no guaranteed checkpoint since {1.name} "
|
|
244
259
|
"on line {1.lineno}."
|
|
245
260
|
),
|
|
261
|
+
"ASYNC912": (
|
|
262
|
+
"CancelScope with no guaranteed checkpoint. This makes it potentially "
|
|
263
|
+
"impossible to cancel."
|
|
264
|
+
),
|
|
265
|
+
"ASYNC100": (
|
|
266
|
+
"{0}.{1} context contains no checkpoints, remove the context or add"
|
|
267
|
+
" `await {0}.lowlevel.checkpoint()`."
|
|
268
|
+
),
|
|
246
269
|
}
|
|
247
270
|
|
|
248
271
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
@@ -256,15 +279,24 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
256
279
|
self.loop_state = LoopState()
|
|
257
280
|
self.try_state = TryState()
|
|
258
281
|
|
|
282
|
+
# ASYNC100
|
|
283
|
+
self.has_checkpoint_stack: list[bool] = []
|
|
284
|
+
self.node_dict: dict[cst.With, list[AttributeCall]] = {}
|
|
285
|
+
|
|
259
286
|
def should_autofix(self, node: cst.CSTNode, code: str | None = None) -> bool:
|
|
287
|
+
if code is None: # pragma: no branch
|
|
288
|
+
code = "ASYNC911" if self.has_yield else "ASYNC910"
|
|
289
|
+
|
|
260
290
|
return (
|
|
261
291
|
not self.noautofix
|
|
262
|
-
and super().should_autofix(
|
|
263
|
-
node, "ASYNC911" if self.has_yield else "ASYNC910"
|
|
264
|
-
)
|
|
292
|
+
and super().should_autofix(node, code)
|
|
265
293
|
and self.library != ("asyncio",)
|
|
266
294
|
)
|
|
267
295
|
|
|
296
|
+
def checkpoint(self) -> None:
|
|
297
|
+
self.uncheckpointed_statements = set()
|
|
298
|
+
self.has_checkpoint_stack = [True] * len(self.has_checkpoint_stack)
|
|
299
|
+
|
|
268
300
|
def checkpoint_statement(self) -> cst.SimpleStatementLine:
|
|
269
301
|
return checkpoint_statement(self.library[0])
|
|
270
302
|
|
|
@@ -283,9 +315,11 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
283
315
|
"uncheckpointed_statements",
|
|
284
316
|
"loop_state",
|
|
285
317
|
"try_state",
|
|
318
|
+
"has_checkpoint_stack",
|
|
286
319
|
copy=True,
|
|
287
320
|
)
|
|
288
321
|
self.uncheckpointed_statements = set()
|
|
322
|
+
self.has_checkpoint_stack = []
|
|
289
323
|
self.has_yield = self.safe_decorator = False
|
|
290
324
|
self.loop_state = LoopState()
|
|
291
325
|
|
|
@@ -359,7 +393,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
359
393
|
any_errors = False
|
|
360
394
|
# raise the actual errors
|
|
361
395
|
for statement in self.uncheckpointed_statements:
|
|
362
|
-
if statement
|
|
396
|
+
if isinstance(statement, ArtificialStatement):
|
|
363
397
|
continue
|
|
364
398
|
any_errors |= self.error_91x(original_node, statement)
|
|
365
399
|
|
|
@@ -376,6 +410,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
376
410
|
self.add_statement = self.checkpoint_statement()
|
|
377
411
|
# avoid duplicate error messages
|
|
378
412
|
self.uncheckpointed_statements = set()
|
|
413
|
+
# we don't treat it as a checkpoint for ASYNC100
|
|
379
414
|
|
|
380
415
|
# return original node to avoid problems with identity equality
|
|
381
416
|
assert original_node.deep_equals(updated_node)
|
|
@@ -386,7 +421,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
386
421
|
node: cst.Return | cst.FunctionDef | cst.Yield,
|
|
387
422
|
statement: Statement,
|
|
388
423
|
) -> bool:
|
|
389
|
-
assert statement
|
|
424
|
+
assert not isinstance(statement, ArtificialStatement)
|
|
390
425
|
|
|
391
426
|
if isinstance(node, cst.FunctionDef):
|
|
392
427
|
msg = "exit"
|
|
@@ -407,7 +442,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
407
442
|
# so only set checkpoint after the await node
|
|
408
443
|
|
|
409
444
|
# all nodes are now checkpointed
|
|
410
|
-
self.
|
|
445
|
+
self.checkpoint()
|
|
411
446
|
return updated_node
|
|
412
447
|
|
|
413
448
|
# raising exception means we don't need to checkpoint so we can treat it as one
|
|
@@ -419,9 +454,54 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
419
454
|
# missing-checkpoint warning when there might in fact be one (i.e. a false alarm).
|
|
420
455
|
def visit_With_body(self, node: cst.With):
|
|
421
456
|
if getattr(node, "asynchronous", None):
|
|
422
|
-
self.
|
|
423
|
-
|
|
424
|
-
|
|
457
|
+
self.checkpoint()
|
|
458
|
+
if res := (
|
|
459
|
+
with_has_call(node, *cancel_scope_names)
|
|
460
|
+
or with_has_call(
|
|
461
|
+
node, "timeout", "timeout_at", base=("asyncio", "asyncio.timeouts")
|
|
462
|
+
)
|
|
463
|
+
):
|
|
464
|
+
pos = self.get_metadata(PositionProvider, node).start # pyright: ignore
|
|
465
|
+
line: int = pos.line # pyright: ignore
|
|
466
|
+
column: int = pos.column # pyright: ignore
|
|
467
|
+
self.uncheckpointed_statements.add(
|
|
468
|
+
ArtificialStatement("with", line, column)
|
|
469
|
+
)
|
|
470
|
+
self.node_dict[node] = res
|
|
471
|
+
self.has_checkpoint_stack.append(False)
|
|
472
|
+
else:
|
|
473
|
+
self.has_checkpoint_stack.append(True)
|
|
474
|
+
|
|
475
|
+
def leave_With(self, original_node: cst.With, updated_node: cst.With):
|
|
476
|
+
# Uses leave_With instead of leave_With_body because we need access to both
|
|
477
|
+
# original and updated node
|
|
478
|
+
# ASYNC100
|
|
479
|
+
if not self.has_checkpoint_stack.pop():
|
|
480
|
+
autofix = len(updated_node.items) == 1
|
|
481
|
+
for res in self.node_dict[original_node]:
|
|
482
|
+
# bypass 910 & 911's should_autofix logic, which excludes asyncio
|
|
483
|
+
# (TODO: and uses self.noautofix ... which I don't remember what it's for)
|
|
484
|
+
autofix &= self.error(
|
|
485
|
+
res.node, res.base, res.function, error_code="ASYNC100"
|
|
486
|
+
) and super().should_autofix(res.node, code="ASYNC100")
|
|
487
|
+
|
|
488
|
+
if autofix:
|
|
489
|
+
return flatten_preserving_comments(updated_node)
|
|
490
|
+
# ASYNC912
|
|
491
|
+
else:
|
|
492
|
+
pos = self.get_metadata( # pyright: ignore
|
|
493
|
+
PositionProvider, original_node
|
|
494
|
+
).start # pyright: ignore
|
|
495
|
+
line: int = pos.line # pyright: ignore
|
|
496
|
+
column: int = pos.column # pyright: ignore
|
|
497
|
+
s = ArtificialStatement("with", line, column)
|
|
498
|
+
if s in self.uncheckpointed_statements:
|
|
499
|
+
self.uncheckpointed_statements.remove(s)
|
|
500
|
+
for res in self.node_dict[original_node]:
|
|
501
|
+
self.error(res.node, error_code="ASYNC912")
|
|
502
|
+
if getattr(original_node, "asynchronous", None):
|
|
503
|
+
self.checkpoint()
|
|
504
|
+
return updated_node
|
|
425
505
|
|
|
426
506
|
# error if no checkpoint since earlier yield or function entry
|
|
427
507
|
def leave_Yield(
|
|
@@ -431,6 +511,10 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
431
511
|
return updated_node
|
|
432
512
|
self.has_yield = True
|
|
433
513
|
|
|
514
|
+
# Treat as a checkpoint for ASYNC100, since the context we yield to
|
|
515
|
+
# may checkpoint.
|
|
516
|
+
self.has_checkpoint_stack = [True] * len(self.has_checkpoint_stack)
|
|
517
|
+
|
|
434
518
|
if self.check_function_exit(original_node) and self.should_autofix(
|
|
435
519
|
original_node
|
|
436
520
|
):
|
|
@@ -605,7 +689,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
605
689
|
# appropriate errors if the loop doesn't checkpoint
|
|
606
690
|
|
|
607
691
|
if getattr(node, "asynchronous", None):
|
|
608
|
-
self.
|
|
692
|
+
self.checkpoint()
|
|
609
693
|
else:
|
|
610
694
|
self.uncheckpointed_statements = {ARTIFICIAL_STATEMENT}
|
|
611
695
|
|
|
@@ -651,7 +735,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
651
735
|
# AsyncFor guarantees checkpoint on running out of iterable
|
|
652
736
|
# so reset checkpoint state at end of loop. (but not state at break)
|
|
653
737
|
if getattr(node, "asynchronous", None):
|
|
654
|
-
self.
|
|
738
|
+
self.checkpoint()
|
|
655
739
|
else:
|
|
656
740
|
# enter orelse with worst case:
|
|
657
741
|
# loop body might execute fully before entering orelse
|
|
@@ -780,7 +864,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
780
864
|
|
|
781
865
|
# if async comprehension, checkpoint
|
|
782
866
|
if node.asynchronous:
|
|
783
|
-
self.
|
|
867
|
+
self.checkpoint()
|
|
784
868
|
self.comp_unknown = False
|
|
785
869
|
return False
|
|
786
870
|
|
|
@@ -104,17 +104,29 @@ class Visitor112(Flake8AsyncVisitor):
|
|
|
104
104
|
continue
|
|
105
105
|
var_name = item.optional_vars.id
|
|
106
106
|
|
|
107
|
-
# check for trio.open_nursery
|
|
107
|
+
# check for trio.open_nursery and anyio.create_task_group
|
|
108
108
|
nursery = get_matching_call(
|
|
109
|
-
item.context_expr, "open_nursery", base=
|
|
110
|
-
)
|
|
109
|
+
item.context_expr, "open_nursery", base="trio"
|
|
110
|
+
) or get_matching_call(item.context_expr, "create_task_group", base="anyio")
|
|
111
|
+
start_methods: tuple[str, ...] = ("start", "start_soon")
|
|
112
|
+
if nursery is None:
|
|
113
|
+
# check for asyncio.TaskGroup
|
|
114
|
+
nursery = get_matching_call(
|
|
115
|
+
item.context_expr, "TaskGroup", base="asyncio"
|
|
116
|
+
)
|
|
117
|
+
if nursery is None:
|
|
118
|
+
continue
|
|
119
|
+
start_methods = ("create_task",)
|
|
120
|
+
|
|
121
|
+
body_call = node.body[0].value
|
|
122
|
+
if isinstance(body_call, ast.Await):
|
|
123
|
+
body_call = body_call.value
|
|
111
124
|
|
|
112
125
|
# `isinstance(..., ast.Call)` is done in get_matching_call
|
|
113
|
-
body_call = cast("ast.Call",
|
|
126
|
+
body_call = cast("ast.Call", body_call)
|
|
114
127
|
|
|
115
128
|
if (
|
|
116
|
-
|
|
117
|
-
and get_matching_call(body_call, "start", "start_soon", base=var_name)
|
|
129
|
+
get_matching_call(body_call, *start_methods, base=var_name)
|
|
118
130
|
# check for presence of <X> as parameter
|
|
119
131
|
and not any(
|
|
120
132
|
(isinstance(n, ast.Name) and n.id == var_name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flake8-async
|
|
3
|
-
Version: 24.
|
|
3
|
+
Version: 24.5.2
|
|
4
4
|
Summary: A highly opinionated flake8 plugin for Trio-related problems.
|
|
5
5
|
Home-page: https://github.com/python-trio/flake8-async
|
|
6
6
|
Author: Zac Hatfield-Dodds, John Litborn, and Contributors
|
|
@@ -89,6 +89,7 @@ Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
|
|
|
89
89
|
- **ASYNC910**: Exit or `return` from async function with no guaranteed checkpoint or exception since function definition. You might want to enable this on a codebase to make it easier to reason about checkpoints, and make the logic of ASYNC911 correct.
|
|
90
90
|
- **ASYNC911**: Exit, `yield` or `return` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
|
|
91
91
|
Checkpoints are `await`, `async for`, and `async with` (on one of enter/exit).
|
|
92
|
+
- **ASYNC912**: Timeout/Cancelscope has no awaits that are guaranteed to run. If the scope has no checkpoints at all, then `ASYNC100` will be raised instead.
|
|
92
93
|
|
|
93
94
|
### Removed Warnings
|
|
94
95
|
- **TRIOxxx**: All error codes are now renamed ASYNCxxx
|
|
@@ -110,7 +111,7 @@ adding the following to your `.pre-commit-config.yaml`:
|
|
|
110
111
|
minimum_pre_commit_version: '2.9.0'
|
|
111
112
|
repos:
|
|
112
113
|
- repo: https://github.com/python-trio/flake8-async
|
|
113
|
-
rev: 24.
|
|
114
|
+
rev: 24.5.2
|
|
114
115
|
hooks:
|
|
115
116
|
- id: flake8-async
|
|
116
117
|
# args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
|
|
@@ -225,6 +226,15 @@ async def my_function():
|
|
|
225
226
|
# Changelog
|
|
226
227
|
*[CalVer, YY.month.patch](https://calver.org/)*
|
|
227
228
|
|
|
229
|
+
## 24.5.2
|
|
230
|
+
- ASYNC101 now also warns on anyio & asyncio taskgroup.s
|
|
231
|
+
- Fixed a bug where ASYNC101 and ASYNC91x would not recognize decorators with parameters directly imported. I.e. `@fixture(...)` will now suppress errors.
|
|
232
|
+
|
|
233
|
+
## 24.5.1
|
|
234
|
+
- Add ASYNC912: no checkpoints in with statement are guaranteed to run.
|
|
235
|
+
- ASYNC100 now properly treats async for comprehensions as checkpoints.
|
|
236
|
+
- ASYNC100 now supports autofixing on asyncio.
|
|
237
|
+
|
|
228
238
|
## 24.4.2
|
|
229
239
|
- Add ASYNC119: yield in contextmanager in async generator.
|
|
230
240
|
|