flake8-async 24.11.4__tar.gz → 25.2.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.11.4 → flake8_async-25.2.2}/PKG-INFO +12 -3
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/__init__.py +1 -1
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor103_104.py +2 -2
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor2xx.py +4 -2
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor91x.py +141 -25
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitors.py +33 -16
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async.egg-info/PKG-INFO +12 -3
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async.egg-info/SOURCES.txt +6 -10
- {flake8_async-24.11.4 → flake8_async-25.2.2}/pyproject.toml +3 -2
- {flake8_async-24.11.4 → flake8_async-25.2.2}/setup.py +1 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async100.py +74 -0
- flake8_async-25.2.2/tests/autofix_files/async124.py +159 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async910.py +21 -2
- flake8_async-25.2.2/tests/autofix_files/async913_trio_anyio.py +12 -0
- flake8_async-24.11.4/tests/test_changelog_and_version.py → flake8_async-25.2.2/tests/check_changelog_and_version.py +26 -10
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async100.py +74 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async101.py +11 -4
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async111.py +0 -2
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async112.py +0 -1
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async112_asyncio.py +1 -1
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async113.py +32 -15
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async113_trio.py +5 -9
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async121.py +0 -1
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async121_anyio.py +0 -1
- flake8_async-25.2.2/tests/eval_files/async124.py +145 -0
- flake8_async-25.2.2/tests/eval_files/async124_no_autofix.py +9 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async251.py +0 -2
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async910.py +20 -2
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async912.py +8 -1
- flake8_async-25.2.2/tests/eval_files/async913_trio_anyio.py +11 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/test_flake8_async.py +25 -2
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/trio_options.py +8 -1
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tox.ini +1 -1
- flake8_async-24.11.4/tests/autofix_files/async100_anyio.py +0 -26
- flake8_async-24.11.4/tests/autofix_files/async100_trio.py +0 -75
- flake8_async-24.11.4/tests/eval_files/async100_anyio.py +0 -26
- flake8_async-24.11.4/tests/eval_files/async100_trio.py +0 -75
- flake8_async-24.11.4/tests/eval_files/async101_anyio.py +0 -10
- flake8_async-24.11.4/tests/eval_files/async101_trio.py +0 -17
- flake8_async-24.11.4/tests/eval_files/async111_anyio.py +0 -21
- flake8_async-24.11.4/tests/eval_files/async112_anyio.py +0 -28
- flake8_async-24.11.4/tests/eval_files/async113_anyio.py +0 -22
- {flake8_async-24.11.4 → flake8_async-25.2.2}/CHANGELOG.md +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/CONTRIBUTING.md +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/LICENSE +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/MANIFEST.in +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/README.md +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/__main__.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/base.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/runner.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/__init__.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/flake8asyncvisitor.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/helpers.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor101.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor102_120.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor105.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor111.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor118.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor123.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async/visitors/visitor_utility.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async.egg-info/dependency_links.txt +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async.egg-info/entry_points.txt +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async.egg-info/not-zip-safe +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async.egg-info/requires.txt +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/flake8_async.egg-info/top_level.txt +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/setup.cfg +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/__init__.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async100_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async100_simple_autofix.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async910_insert_library.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async911.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async911_insert_library.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async913.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/async91x_autofix.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/exception_suppress_context_manager.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/exception_suppress_context_manager_import_star.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/noqa.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/autofix_files/noqa_testing.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/conftest.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/anyio_trio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async100_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async100_noautofix.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async100_simple_autofix.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async101_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async102.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async102_aclose.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async102_aclose_args.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async102_anyio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async102_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async102_trio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async103.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async103_all_imported.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async103_both_imported.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async103_no_104.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async103_trio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async104.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async104_anyio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async104_trio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async105.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async105_anyio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async106.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async109.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async110.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async111_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async113_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async114.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async115.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async116.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async118.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async119.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async120.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async121_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async122.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async123.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async123_py311.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async200.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async210.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async211.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async212.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async22x.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async22x_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async232.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async232_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async23x.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async23x_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async240.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async250.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async250_multi_library.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async251_multi_library.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async300.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async900.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async910_insert_library.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async911.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async911_insert_library.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async912_asyncio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async913.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async91x_autofix.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/async91x_noautofix.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/exception_suppress_context_manager.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/exception_suppress_context_manager_import_star.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/no_library.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/noqa.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/noqa_no_autofix.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/noqa_testing.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/eval_files/trio_anyio.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/test_all_visitors_imported.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/test_config_and_args.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/test_decorator.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/test_exception_on_invalid_code.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/test_formatting.py +0 -0
- {flake8_async-24.11.4 → flake8_async-25.2.2}/tests/test_messages_documented.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: flake8-async
|
|
3
|
-
Version:
|
|
3
|
+
Version: 25.2.2
|
|
4
4
|
Summary: A highly opinionated flake8 plugin for Trio-related problems.
|
|
5
5
|
Author: Zac Hatfield-Dodds, John Litborn, and Contributors
|
|
6
6
|
Author-email: zac@zhd.dev
|
|
@@ -21,9 +21,18 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Requires-Python: >=3.9
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
24
|
Requires-Dist: flake8>=6
|
|
26
25
|
Requires-Dist: libcst>=1.0.1
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: project-url
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
27
36
|
|
|
28
37
|
[](https://flake8-async.readthedocs.io)
|
|
29
38
|
[](https://pypi.org/project/flake8-async)
|
|
@@ -51,9 +51,9 @@ _error_codes = {
|
|
|
51
51
|
"ASYNC103": _async103_common_msg,
|
|
52
52
|
"ASYNC104": "Cancelled (and therefore BaseException) must be re-raised.",
|
|
53
53
|
}
|
|
54
|
-
for poss_library in _suggestion_dict:
|
|
54
|
+
for poss_library, _lib_suggest in _suggestion_dict.items():
|
|
55
55
|
_error_codes[f"ASYNC103_{'_'.join(poss_library)}"] = (
|
|
56
|
-
_async103_common_msg + _suggestion.format(
|
|
56
|
+
_async103_common_msg + _suggestion.format(_lib_suggest)
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
|
|
@@ -186,7 +186,9 @@ class Visitor22X(Visitor200):
|
|
|
186
186
|
"Sync call {} in async function, use "
|
|
187
187
|
"`asyncio.create_subprocess_[exec/shell]."
|
|
188
188
|
),
|
|
189
|
-
"ASYNC222":
|
|
189
|
+
"ASYNC222": (
|
|
190
|
+
"Sync call {} in async function, wrap in `{}.to_thread.run_sync()`."
|
|
191
|
+
),
|
|
190
192
|
"ASYNC222_asyncio": (
|
|
191
193
|
"Sync call {} in async function, use `asyncio.loop.run_in_executor`."
|
|
192
194
|
),
|
|
@@ -397,7 +399,7 @@ wrappers: Mapping[str, str] = {
|
|
|
397
399
|
@error_class
|
|
398
400
|
class Visitor25X(Visitor200):
|
|
399
401
|
error_codes: Mapping[str, str] = {
|
|
400
|
-
"ASYNC250":
|
|
402
|
+
"ASYNC250": "Blocking sync call `input()` in async function. Wrap in `{}`.",
|
|
401
403
|
"ASYNC251": (
|
|
402
404
|
"Blocking sync call `time.sleep(...)` in async function."
|
|
403
405
|
" Use `await {}.sleep(...)`."
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
"""Contains Visitor91X.
|
|
1
|
+
"""Contains Visitor91X and Visitor124.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Visitor91X contains checks for
|
|
4
|
+
* ASYNC100 cancel-scope-no-checkpoint
|
|
5
|
+
* ASYNC910 async-function-no-checkpoint
|
|
6
|
+
* ASYNC911 async-generator-no-checkpoint
|
|
7
|
+
* ASYNC912 cancel-scope-no-guaranteed-checkpoint
|
|
8
|
+
* ASYNC913 indefinite-loop-no-guaranteed-checkpoint
|
|
9
|
+
|
|
10
|
+
Visitor124 contains
|
|
11
|
+
* ASYNC124 async-function-could-be-sync
|
|
6
12
|
"""
|
|
7
13
|
|
|
8
14
|
from __future__ import annotations
|
|
@@ -64,6 +70,92 @@ def func_empty_body(node: cst.FunctionDef) -> bool:
|
|
|
64
70
|
)
|
|
65
71
|
|
|
66
72
|
|
|
73
|
+
# this could've been implemented as part of visitor91x, but /shrug
|
|
74
|
+
@error_class_cst
|
|
75
|
+
class Visitor124(Flake8AsyncVisitor_cst):
|
|
76
|
+
error_codes: Mapping[str, str] = {
|
|
77
|
+
"ASYNC124": (
|
|
78
|
+
"Async function with no `await` could be sync."
|
|
79
|
+
" Async functions are more expensive to call."
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
84
|
+
super().__init__(*args, **kwargs)
|
|
85
|
+
self.has_await = False
|
|
86
|
+
self.in_class = False
|
|
87
|
+
|
|
88
|
+
def visit_ClassDef(self, node: cst.ClassDef):
|
|
89
|
+
self.save_state(node, "in_class", copy=False)
|
|
90
|
+
self.in_class = True
|
|
91
|
+
|
|
92
|
+
def leave_ClassDef(
|
|
93
|
+
self, original_node: cst.ClassDef, updated_node: cst.ClassDef
|
|
94
|
+
) -> cst.ClassDef:
|
|
95
|
+
self.restore_state(original_node)
|
|
96
|
+
return updated_node
|
|
97
|
+
|
|
98
|
+
# await in sync defs are not valid, but handling this will make ASYNC124
|
|
99
|
+
# correctly pop up in parent func as if the child function was async
|
|
100
|
+
def visit_FunctionDef(self, node: cst.FunctionDef):
|
|
101
|
+
# default values are evaluated in parent scope
|
|
102
|
+
# this visitor has no autofixes, so we can throw away return value
|
|
103
|
+
_ = node.params.visit(self)
|
|
104
|
+
|
|
105
|
+
self.save_state(node, "has_await", "in_class", copy=False)
|
|
106
|
+
|
|
107
|
+
# ignore class methods
|
|
108
|
+
self.has_await = self.in_class
|
|
109
|
+
|
|
110
|
+
# but not nested functions
|
|
111
|
+
self.in_class = False
|
|
112
|
+
|
|
113
|
+
_ = node.body.visit(self)
|
|
114
|
+
|
|
115
|
+
# we've manually visited subnodes (that we care about).
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def leave_FunctionDef(
|
|
119
|
+
self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
|
|
120
|
+
) -> cst.FunctionDef:
|
|
121
|
+
if (
|
|
122
|
+
original_node.asynchronous is not None
|
|
123
|
+
and not self.has_await
|
|
124
|
+
and not func_empty_body(original_node)
|
|
125
|
+
and not func_has_decorator(original_node, "overload")
|
|
126
|
+
# skip functions with @fixture and params since they may be relying
|
|
127
|
+
# on async fixtures.
|
|
128
|
+
and not (
|
|
129
|
+
original_node.params.params
|
|
130
|
+
and func_has_decorator(original_node, "fixture")
|
|
131
|
+
)
|
|
132
|
+
# ignore functions with no_checkpoint_warning_decorators
|
|
133
|
+
and not fnmatch_qualified_name_cst(
|
|
134
|
+
original_node.decorators, *self.options.no_checkpoint_warning_decorators
|
|
135
|
+
)
|
|
136
|
+
):
|
|
137
|
+
self.error(original_node)
|
|
138
|
+
self.restore_state(original_node)
|
|
139
|
+
return updated_node
|
|
140
|
+
|
|
141
|
+
def visit_Await(self, node: cst.Await):
|
|
142
|
+
self.has_await = True
|
|
143
|
+
|
|
144
|
+
def visit_With(self, node: cst.With | cst.For | cst.CompFor):
|
|
145
|
+
if node.asynchronous is not None:
|
|
146
|
+
self.has_await = True
|
|
147
|
+
|
|
148
|
+
visit_For = visit_With
|
|
149
|
+
visit_CompFor = visit_With
|
|
150
|
+
|
|
151
|
+
# The generator target will be immediately evaluated, but the other
|
|
152
|
+
# elements will not be evaluated at the point of defining the GenExp.
|
|
153
|
+
# To consume those needs an explicit syntactic checkpoint
|
|
154
|
+
def visit_GeneratorExp(self, node: cst.GeneratorExp):
|
|
155
|
+
node.for_in.iter.visit(self)
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
67
159
|
@dataclass
|
|
68
160
|
class LoopState:
|
|
69
161
|
infinite_loop: bool = False
|
|
@@ -262,10 +354,10 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
262
354
|
"on line {1.lineno}."
|
|
263
355
|
),
|
|
264
356
|
"ASYNC912": (
|
|
265
|
-
"CancelScope with no guaranteed
|
|
357
|
+
"CancelScope with no guaranteed cancel point. This makes it potentially "
|
|
266
358
|
"impossible to cancel."
|
|
267
359
|
),
|
|
268
|
-
"ASYNC913":
|
|
360
|
+
"ASYNC913": "Indefinite loop with no guaranteed cancel points.",
|
|
269
361
|
"ASYNC100": (
|
|
270
362
|
"{0}.{1} context contains no checkpoints, remove the context or add"
|
|
271
363
|
" `await {0}.lowlevel.checkpoint()`."
|
|
@@ -275,7 +367,6 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
275
367
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
276
368
|
super().__init__(*args, **kwargs)
|
|
277
369
|
self.has_yield = False
|
|
278
|
-
self.safe_decorator = False
|
|
279
370
|
self.async_function = False
|
|
280
371
|
self.uncheckpointed_statements: set[Statement] = set()
|
|
281
372
|
self.comp_unknown = False
|
|
@@ -291,6 +382,9 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
291
382
|
# --exception-suppress-context-manager
|
|
292
383
|
self.suppress_imported_as: list[str] = []
|
|
293
384
|
|
|
385
|
+
# used to transfer new body between visit_FunctionDef and leave_FunctionDef
|
|
386
|
+
self.new_body: cst.BaseSuite | None = None
|
|
387
|
+
|
|
294
388
|
def should_autofix(self, node: cst.CSTNode, code: str | None = None) -> bool:
|
|
295
389
|
if code is None: # pragma: no branch
|
|
296
390
|
code = "ASYNC911" if self.has_yield else "ASYNC910"
|
|
@@ -307,10 +401,16 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
307
401
|
self.taskgroup_has_start_soon.clear()
|
|
308
402
|
|
|
309
403
|
def checkpoint_schedule_point(self) -> None:
|
|
310
|
-
|
|
404
|
+
# ASYNC912&ASYNC913 only cares about cancel points, so don't remove
|
|
405
|
+
# them if we only do a schedule point
|
|
406
|
+
self.uncheckpointed_statements = {
|
|
407
|
+
s
|
|
408
|
+
for s in self.uncheckpointed_statements
|
|
409
|
+
if isinstance(s, ArtificialStatement)
|
|
410
|
+
}
|
|
311
411
|
|
|
312
412
|
def checkpoint(self) -> None:
|
|
313
|
-
self.
|
|
413
|
+
self.uncheckpointed_statements = set()
|
|
314
414
|
self.checkpoint_cancel_point()
|
|
315
415
|
|
|
316
416
|
def checkpoint_statement(self) -> cst.SimpleStatementLine:
|
|
@@ -349,6 +449,10 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
349
449
|
return
|
|
350
450
|
|
|
351
451
|
def visit_FunctionDef(self, node: cst.FunctionDef) -> bool:
|
|
452
|
+
# `await` in default values happen in parent scope
|
|
453
|
+
# we also know we don't ever modify parameters so we can ignore the return value
|
|
454
|
+
_ = node.params.visit(self)
|
|
455
|
+
|
|
352
456
|
# don't lint functions whose bodies solely consist of pass or ellipsis
|
|
353
457
|
# @overload functions are also guaranteed to be empty
|
|
354
458
|
# we also ignore pytest fixtures
|
|
@@ -358,7 +462,6 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
358
462
|
self.save_state(
|
|
359
463
|
node,
|
|
360
464
|
"has_yield",
|
|
361
|
-
"safe_decorator",
|
|
362
465
|
"async_function",
|
|
363
466
|
"uncheckpointed_statements",
|
|
364
467
|
# comp_unknown does not need to be saved
|
|
@@ -370,11 +473,11 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
370
473
|
"suppress_imported_as", # a copy is saved, but state is not reset
|
|
371
474
|
copy=True,
|
|
372
475
|
)
|
|
373
|
-
self.has_yield = self.safe_decorator = False
|
|
374
476
|
self.uncheckpointed_statements = set()
|
|
477
|
+
self.has_checkpoint_stack = []
|
|
478
|
+
self.has_yield = False
|
|
375
479
|
self.loop_state = LoopState()
|
|
376
480
|
# try_state is reset upon entering try
|
|
377
|
-
self.has_checkpoint_stack = []
|
|
378
481
|
self.taskgroup_has_start_soon = {}
|
|
379
482
|
|
|
380
483
|
self.async_function = (
|
|
@@ -383,36 +486,49 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
383
486
|
node.decorators, *self.options.no_checkpoint_warning_decorators
|
|
384
487
|
)
|
|
385
488
|
)
|
|
386
|
-
if
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
489
|
+
# only visit subnodes if there is an async function defined inside
|
|
490
|
+
# this should improve performance on codebases with many sync functions
|
|
491
|
+
if not self.async_function and not any(
|
|
492
|
+
m.findall(node, m.FunctionDef(asynchronous=m.Asynchronous()))
|
|
493
|
+
):
|
|
494
|
+
return False
|
|
390
495
|
|
|
391
496
|
pos = self.get_metadata(PositionProvider, node).start # type: ignore
|
|
392
497
|
self.uncheckpointed_statements = {
|
|
393
498
|
Statement("function definition", pos.line, pos.column) # type: ignore
|
|
394
499
|
}
|
|
395
|
-
|
|
500
|
+
|
|
501
|
+
# visit body
|
|
502
|
+
# we're not gonna get FlattenSentinel or RemovalSentinel
|
|
503
|
+
self.new_body = cast(cst.BaseSuite, node.body.visit(self))
|
|
504
|
+
|
|
505
|
+
# we know that leave_FunctionDef for this FunctionDef will run immediately after
|
|
506
|
+
# this function exits so we don't need to worry about save_state for new_body
|
|
507
|
+
return False
|
|
396
508
|
|
|
397
509
|
def leave_FunctionDef(
|
|
398
510
|
self, original_node: cst.FunctionDef, updated_node: cst.FunctionDef
|
|
399
511
|
) -> cst.FunctionDef:
|
|
400
512
|
if (
|
|
401
|
-
self.
|
|
513
|
+
self.new_body is not None
|
|
514
|
+
and self.async_function
|
|
402
515
|
# updated_node does not have a position, so we must send original_node
|
|
403
516
|
and self.check_function_exit(original_node)
|
|
404
517
|
and self.should_autofix(original_node)
|
|
405
|
-
and isinstance(
|
|
518
|
+
and isinstance(self.new_body, cst.IndentedBlock)
|
|
406
519
|
):
|
|
407
520
|
# insert checkpoint at the end of body
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
updated_node = updated_node.with_changes(body=indentedblock)
|
|
521
|
+
new_body_block = list(self.new_body.body)
|
|
522
|
+
new_body_block.append(self.checkpoint_statement())
|
|
523
|
+
self.new_body = self.new_body.with_changes(body=new_body_block)
|
|
412
524
|
|
|
413
525
|
self.ensure_imported_library()
|
|
414
526
|
|
|
527
|
+
if self.new_body is not None:
|
|
528
|
+
updated_node = updated_node.with_changes(body=self.new_body)
|
|
415
529
|
self.restore_state(original_node)
|
|
530
|
+
# reset self.new_body
|
|
531
|
+
self.new_body = None
|
|
416
532
|
return updated_node
|
|
417
533
|
|
|
418
534
|
# error if function exit/return/yields with uncheckpointed statements
|
|
@@ -1039,8 +1155,8 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
1039
1155
|
return False
|
|
1040
1156
|
|
|
1041
1157
|
# The generator target will be immediately evaluated, but the other
|
|
1042
|
-
# elements will be
|
|
1043
|
-
#
|
|
1158
|
+
# elements will not be evaluated at the point of defining the GenExp.
|
|
1159
|
+
# To consume those needs an explicit syntactic checkpoint
|
|
1044
1160
|
def visit_GeneratorExp(self, node: cst.GeneratorExp):
|
|
1045
1161
|
node.for_in.iter.visit(self)
|
|
1046
1162
|
return False
|
|
@@ -121,10 +121,10 @@ class Visitor112(Flake8AsyncVisitor):
|
|
|
121
121
|
elif get_matching_call(
|
|
122
122
|
item.context_expr, "create_task_group", base="anyio"
|
|
123
123
|
):
|
|
124
|
-
nursery_type = "
|
|
124
|
+
nursery_type = "task group"
|
|
125
125
|
# check for asyncio.TaskGroup
|
|
126
126
|
elif get_matching_call(item.context_expr, "TaskGroup", base="asyncio"):
|
|
127
|
-
nursery_type = "
|
|
127
|
+
nursery_type = "task group"
|
|
128
128
|
start_methods = ("create_task",)
|
|
129
129
|
else:
|
|
130
130
|
# incorrectly marked as not covered on py39
|
|
@@ -151,12 +151,12 @@ class Visitor112(Flake8AsyncVisitor):
|
|
|
151
151
|
|
|
152
152
|
|
|
153
153
|
# used in 113 and 114
|
|
154
|
-
STARTABLE_CALLS = (
|
|
154
|
+
STARTABLE_CALLS = ("serve",)
|
|
155
|
+
TRIO_STARTABLE_CALLS = (
|
|
155
156
|
"run_process",
|
|
156
157
|
"serve_ssl_over_tcp",
|
|
157
158
|
"serve_tcp",
|
|
158
159
|
"serve_listeners",
|
|
159
|
-
"serve",
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
|
|
@@ -201,7 +201,11 @@ class Visitor113(Flake8AsyncVisitor):
|
|
|
201
201
|
if isinstance(n, ast.Name):
|
|
202
202
|
return n.id in startable_list
|
|
203
203
|
if isinstance(n, ast.Attribute):
|
|
204
|
-
return n.attr in startable_list
|
|
204
|
+
return n.attr in startable_list and not (
|
|
205
|
+
n.attr in TRIO_STARTABLE_CALLS
|
|
206
|
+
and isinstance(n.value, ast.Name)
|
|
207
|
+
and n.value.id != "trio"
|
|
208
|
+
)
|
|
205
209
|
if isinstance(n, ast.Call):
|
|
206
210
|
return any(is_startable(nn, *startable_list) for nn in n.args)
|
|
207
211
|
return False
|
|
@@ -213,12 +217,16 @@ class Visitor113(Flake8AsyncVisitor):
|
|
|
213
217
|
):
|
|
214
218
|
return False
|
|
215
219
|
var = ast.unparse(node.value)
|
|
216
|
-
return (
|
|
217
|
-
self.
|
|
218
|
-
in (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
return (
|
|
221
|
+
("trio" in self.library and var.endswith("nursery"))
|
|
222
|
+
or ("anyio" in self.library and var.endswith("task_group"))
|
|
223
|
+
or (
|
|
224
|
+
self.variables.get(var, "")
|
|
225
|
+
in (
|
|
226
|
+
"trio.Nursery",
|
|
227
|
+
"anyio.TaskGroup",
|
|
228
|
+
"asyncio.TaskGroup",
|
|
229
|
+
)
|
|
222
230
|
)
|
|
223
231
|
)
|
|
224
232
|
|
|
@@ -229,6 +237,7 @@ class Visitor113(Flake8AsyncVisitor):
|
|
|
229
237
|
and is_startable(
|
|
230
238
|
node.args[0],
|
|
231
239
|
*STARTABLE_CALLS,
|
|
240
|
+
*TRIO_STARTABLE_CALLS,
|
|
232
241
|
*self.options.startable_in_context_manager,
|
|
233
242
|
)
|
|
234
243
|
):
|
|
@@ -254,7 +263,11 @@ class Visitor114(Flake8AsyncVisitor):
|
|
|
254
263
|
for n in self.walk(*node.args.args, *node.args.kwonlyargs)
|
|
255
264
|
) and not any(
|
|
256
265
|
node.name == opt
|
|
257
|
-
for opt in (
|
|
266
|
+
for opt in (
|
|
267
|
+
*self.options.startable_in_context_manager,
|
|
268
|
+
*STARTABLE_CALLS,
|
|
269
|
+
*TRIO_STARTABLE_CALLS,
|
|
270
|
+
)
|
|
258
271
|
):
|
|
259
272
|
self.error(node, node.name)
|
|
260
273
|
|
|
@@ -319,8 +332,10 @@ class Visitor116(Flake8AsyncVisitor):
|
|
|
319
332
|
@error_class
|
|
320
333
|
class Visitor119(Flake8AsyncVisitor):
|
|
321
334
|
error_codes: Mapping[str, str] = {
|
|
322
|
-
"ASYNC119":
|
|
323
|
-
|
|
335
|
+
"ASYNC119": (
|
|
336
|
+
"Yield in contextmanager in async generator might not trigger"
|
|
337
|
+
" cleanup. Use `@asynccontextmanager` or refactor."
|
|
338
|
+
)
|
|
324
339
|
}
|
|
325
340
|
|
|
326
341
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
@@ -490,8 +505,10 @@ class Visitor300(Flake8AsyncVisitor_cst):
|
|
|
490
505
|
@disabled_by_default
|
|
491
506
|
class Visitor900(Flake8AsyncVisitor):
|
|
492
507
|
error_codes: Mapping[str, str] = {
|
|
493
|
-
"ASYNC900":
|
|
494
|
-
|
|
508
|
+
"ASYNC900": (
|
|
509
|
+
"Async generator not allowed, unless transformed "
|
|
510
|
+
"by a known decorator (one of: {})."
|
|
511
|
+
)
|
|
495
512
|
}
|
|
496
513
|
|
|
497
514
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: flake8-async
|
|
3
|
-
Version:
|
|
3
|
+
Version: 25.2.2
|
|
4
4
|
Summary: A highly opinionated flake8 plugin for Trio-related problems.
|
|
5
5
|
Author: Zac Hatfield-Dodds, John Litborn, and Contributors
|
|
6
6
|
Author-email: zac@zhd.dev
|
|
@@ -21,9 +21,18 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Requires-Python: >=3.9
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
24
|
Requires-Dist: flake8>=6
|
|
26
25
|
Requires-Dist: libcst>=1.0.1
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: project-url
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
27
36
|
|
|
28
37
|
[](https://flake8-async.readthedocs.io)
|
|
29
38
|
[](https://pypi.org/project/flake8-async)
|
|
@@ -32,9 +32,9 @@ flake8_async/visitors/visitor91x.py
|
|
|
32
32
|
flake8_async/visitors/visitor_utility.py
|
|
33
33
|
flake8_async/visitors/visitors.py
|
|
34
34
|
tests/__init__.py
|
|
35
|
+
tests/check_changelog_and_version.py
|
|
35
36
|
tests/conftest.py
|
|
36
37
|
tests/test_all_visitors_imported.py
|
|
37
|
-
tests/test_changelog_and_version.py
|
|
38
38
|
tests/test_config_and_args.py
|
|
39
39
|
tests/test_decorator.py
|
|
40
40
|
tests/test_exception_on_invalid_code.py
|
|
@@ -43,15 +43,15 @@ tests/test_formatting.py
|
|
|
43
43
|
tests/test_messages_documented.py
|
|
44
44
|
tests/trio_options.py
|
|
45
45
|
tests/autofix_files/async100.py
|
|
46
|
-
tests/autofix_files/async100_anyio.py
|
|
47
46
|
tests/autofix_files/async100_asyncio.py
|
|
48
47
|
tests/autofix_files/async100_simple_autofix.py
|
|
49
|
-
tests/autofix_files/
|
|
48
|
+
tests/autofix_files/async124.py
|
|
50
49
|
tests/autofix_files/async910.py
|
|
51
50
|
tests/autofix_files/async910_insert_library.py
|
|
52
51
|
tests/autofix_files/async911.py
|
|
53
52
|
tests/autofix_files/async911_insert_library.py
|
|
54
53
|
tests/autofix_files/async913.py
|
|
54
|
+
tests/autofix_files/async913_trio_anyio.py
|
|
55
55
|
tests/autofix_files/async91x_autofix.py
|
|
56
56
|
tests/autofix_files/exception_suppress_context_manager.py
|
|
57
57
|
tests/autofix_files/exception_suppress_context_manager_import_star.py
|
|
@@ -59,15 +59,11 @@ tests/autofix_files/noqa.py
|
|
|
59
59
|
tests/autofix_files/noqa_testing.py
|
|
60
60
|
tests/eval_files/anyio_trio.py
|
|
61
61
|
tests/eval_files/async100.py
|
|
62
|
-
tests/eval_files/async100_anyio.py
|
|
63
62
|
tests/eval_files/async100_asyncio.py
|
|
64
63
|
tests/eval_files/async100_noautofix.py
|
|
65
64
|
tests/eval_files/async100_simple_autofix.py
|
|
66
|
-
tests/eval_files/async100_trio.py
|
|
67
65
|
tests/eval_files/async101.py
|
|
68
|
-
tests/eval_files/async101_anyio.py
|
|
69
66
|
tests/eval_files/async101_asyncio.py
|
|
70
|
-
tests/eval_files/async101_trio.py
|
|
71
67
|
tests/eval_files/async102.py
|
|
72
68
|
tests/eval_files/async102_aclose.py
|
|
73
69
|
tests/eval_files/async102_aclose_args.py
|
|
@@ -88,13 +84,10 @@ tests/eval_files/async106.py
|
|
|
88
84
|
tests/eval_files/async109.py
|
|
89
85
|
tests/eval_files/async110.py
|
|
90
86
|
tests/eval_files/async111.py
|
|
91
|
-
tests/eval_files/async111_anyio.py
|
|
92
87
|
tests/eval_files/async111_asyncio.py
|
|
93
88
|
tests/eval_files/async112.py
|
|
94
|
-
tests/eval_files/async112_anyio.py
|
|
95
89
|
tests/eval_files/async112_asyncio.py
|
|
96
90
|
tests/eval_files/async113.py
|
|
97
|
-
tests/eval_files/async113_anyio.py
|
|
98
91
|
tests/eval_files/async113_asyncio.py
|
|
99
92
|
tests/eval_files/async113_trio.py
|
|
100
93
|
tests/eval_files/async114.py
|
|
@@ -109,6 +102,8 @@ tests/eval_files/async121_asyncio.py
|
|
|
109
102
|
tests/eval_files/async122.py
|
|
110
103
|
tests/eval_files/async123.py
|
|
111
104
|
tests/eval_files/async123_py311.py
|
|
105
|
+
tests/eval_files/async124.py
|
|
106
|
+
tests/eval_files/async124_no_autofix.py
|
|
112
107
|
tests/eval_files/async200.py
|
|
113
108
|
tests/eval_files/async210.py
|
|
114
109
|
tests/eval_files/async211.py
|
|
@@ -133,6 +128,7 @@ tests/eval_files/async911_insert_library.py
|
|
|
133
128
|
tests/eval_files/async912.py
|
|
134
129
|
tests/eval_files/async912_asyncio.py
|
|
135
130
|
tests/eval_files/async913.py
|
|
131
|
+
tests/eval_files/async913_trio_anyio.py
|
|
136
132
|
tests/eval_files/async91x_autofix.py
|
|
137
133
|
tests/eval_files/async91x_noautofix.py
|
|
138
134
|
tests/eval_files/exception_suppress_context_manager.py
|
|
@@ -17,7 +17,8 @@ ignore-words-list = 'spawnve'
|
|
|
17
17
|
[tool.isort]
|
|
18
18
|
only_modified = true
|
|
19
19
|
profile = "black"
|
|
20
|
-
|
|
20
|
+
# isort is slow to release official support for new python versions
|
|
21
|
+
py_version = "312"
|
|
21
22
|
quiet = true
|
|
22
23
|
skip_gitignore = true
|
|
23
24
|
skip_glob = "tests/*_files/*"
|
|
@@ -32,7 +33,7 @@ warn_unused_ignores = false
|
|
|
32
33
|
|
|
33
34
|
[tool.pyright]
|
|
34
35
|
exclude = ["**/node_modules", "**/__pycache__", "**/.*", "tests/eval_files/*", "tests/autofix_files/*"] # TODO: fix errors in eval/autofix files
|
|
35
|
-
pythonVersion = "3.
|
|
36
|
+
pythonVersion = "3.13"
|
|
36
37
|
reportCallInDefaultInitializer = true
|
|
37
38
|
reportImplicitStringConcatenation = false # black generates implicit string concats
|
|
38
39
|
reportMissingSuperCall = true
|
|
@@ -31,6 +31,7 @@ setup(
|
|
|
31
31
|
"Changelog": "https://flake8-async.readthedocs.io/en/latest/changelog.html",
|
|
32
32
|
},
|
|
33
33
|
license="MIT",
|
|
34
|
+
license_files=[], # https://github.com/pypa/twine/issues/1216
|
|
34
35
|
description="A highly opinionated flake8 plugin for Trio-related problems.",
|
|
35
36
|
zip_safe=False,
|
|
36
37
|
install_requires=["flake8>=6", "libcst>=1.0.1"],
|
|
@@ -140,3 +140,77 @@ async def dont_crash_on_non_name_or_attr_call():
|
|
|
140
140
|
async def another_weird_with_call():
|
|
141
141
|
async with a().b():
|
|
142
142
|
...
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---open_nursery / create_task_group stuff---
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def nursery_no_cancel_point():
|
|
149
|
+
# error: 9, "trio", "CancelScope"
|
|
150
|
+
async with trio.open_nursery():
|
|
151
|
+
...
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# but it is a cancel point if the nursery contains a call to start_soon()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def nursery_start_soon():
|
|
158
|
+
with trio.CancelScope():
|
|
159
|
+
async with trio.open_nursery() as n:
|
|
160
|
+
n.start_soon(trio.sleep, 0)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def nursery_start_soon_misnested():
|
|
164
|
+
async with trio.open_nursery() as n:
|
|
165
|
+
# error: 13, "trio", "CancelScope"
|
|
166
|
+
n.start_soon(trio.sleep, 0)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def nested_scope():
|
|
170
|
+
with trio.CancelScope():
|
|
171
|
+
with trio.CancelScope():
|
|
172
|
+
async with trio.open_nursery() as n:
|
|
173
|
+
n.start_soon(trio.sleep, 0)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def nested_nursery():
|
|
177
|
+
with trio.CancelScope():
|
|
178
|
+
async with trio.open_nursery() as n:
|
|
179
|
+
async with trio.open_nursery() as n2:
|
|
180
|
+
n2.start_soon(trio.sleep, 0)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
async def nested_function_call():
|
|
184
|
+
|
|
185
|
+
# error: 9, "trio", "CancelScope"
|
|
186
|
+
async with trio.open_nursery() as n:
|
|
187
|
+
|
|
188
|
+
def foo():
|
|
189
|
+
n.start_soon(trio.sleep, 0)
|
|
190
|
+
|
|
191
|
+
# a false alarm in case we call foo()... but we can't check if they do
|
|
192
|
+
foo()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# insert cancel point on nursery exit, not at the start_soon call
|
|
196
|
+
async def cancel_point_on_nursery_exit():
|
|
197
|
+
with trio.CancelScope():
|
|
198
|
+
async with trio.open_nursery() as n:
|
|
199
|
+
# error: 17, "trio", "CancelScope"
|
|
200
|
+
n.start_soon(trio.sleep, 0)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# async100 does not consider *redundant* cancel scopes
|
|
204
|
+
async def redundant_cancel_scope():
|
|
205
|
+
with trio.CancelScope():
|
|
206
|
+
with trio.CancelScope():
|
|
207
|
+
await trio.lowlevel.checkpoint()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# but if it did then none of these scopes should be marked redundant
|
|
211
|
+
# The inner checks task startup, the outer checks task exit
|
|
212
|
+
async def nursery_exit_blocks_with_start():
|
|
213
|
+
with trio.CancelScope():
|
|
214
|
+
async with trio.open_nursery() as n:
|
|
215
|
+
with trio.CancelScope():
|
|
216
|
+
await n.start(trio.sleep, 0)
|