flake8-async 25.4.4__tar.gz → 25.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-25.4.4 → flake8_async-25.5.2}/PKG-INFO +1 -1
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/__init__.py +1 -1
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/__init__.py +1 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor102_120.py +1 -11
- flake8_async-25.5.2/flake8_async/visitors/visitor4xx.py +98 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitors.py +38 -14
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async.egg-info/PKG-INFO +1 -1
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async.egg-info/SOURCES.txt +3 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async102.py +2 -1
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async113.py +58 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async113_trio.py +2 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async120.py +10 -0
- flake8_async-25.5.2/tests/eval_files/async400_py311.py +84 -0
- flake8_async-25.5.2/tests/eval_files/async900_trio.py +15 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/test_flake8_async.py +12 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/CHANGELOG.md +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/CONTRIBUTING.md +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/LICENSE +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/MANIFEST.in +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/README.md +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/__main__.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/base.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/runner.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/flake8asyncvisitor.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/helpers.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor101.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor103_104.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor105.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor111.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor118.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor123.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor2xx.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor91x.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async/visitors/visitor_utility.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async.egg-info/dependency_links.txt +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async.egg-info/entry_points.txt +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async.egg-info/not-zip-safe +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async.egg-info/requires.txt +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/flake8_async.egg-info/top_level.txt +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/pyproject.toml +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/setup.cfg +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/setup.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/__init__.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async100.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async100_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async100_simple_autofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async124.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async910.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async910_insert_library.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async911.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async911_insert_library.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async913.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async913_trio_anyio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async91x_autofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async91x_py310.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/async91x_py311.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/exception_suppress_context_manager.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/exception_suppress_context_manager_import_star.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/noqa.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/autofix_files/noqa_testing.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/check_changelog_and_version.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/conftest.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/anyio_trio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async100.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async100_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async100_asyncio_noautofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async100_simple_autofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async101.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async101_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async102_120_py311.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async102_aclose.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async102_aclose_args.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async102_anyio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async102_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async102_trio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async103.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async103_104_py310.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async103_104_py311.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async103_all_imported.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async103_both_imported.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async103_no_104.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async103_trio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async104.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async104_anyio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async104_trio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async105.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async105_anyio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async106.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async109.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async110.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async111.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async111_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async112.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async112_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async113_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async114.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async115.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async116.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async118.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async119.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async121.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async121_anyio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async121_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async122.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async123.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async123_py311.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async124.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async124_no_autofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async125.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async200.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async210.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async211.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async212.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async22x.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async22x_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async232.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async232_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async23x.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async23x_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async240.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async250.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async250_multi_library.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async251.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async251_multi_library.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async300.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async900.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async910.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async910_insert_library.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async911.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async911_insert_library.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async912.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async912_asyncio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async912_py311.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async913.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async913_trio_anyio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async91x_autofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async91x_noautofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async91x_py310.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/async91x_py311.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/exception_suppress_context_manager.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/exception_suppress_context_manager_import_star.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/no_library.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/noqa.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/noqa_no_autofix.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/noqa_testing.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/trio_anyio.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/test_all_visitors_imported.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/test_config_and_args.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/test_decorator.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/test_exception_on_invalid_code.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/test_formatting.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/test_messages_documented.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tests/trio_options.py +0 -0
- {flake8_async-25.4.4 → flake8_async-25.5.2}/tox.ini +0 -0
|
@@ -40,16 +40,6 @@ class Visitor102(Flake8AsyncVisitor):
|
|
|
40
40
|
self.funcname = funcname
|
|
41
41
|
self.variable_name: str | None = None
|
|
42
42
|
self.shielded: bool = False
|
|
43
|
-
self.has_timeout: bool = True
|
|
44
|
-
|
|
45
|
-
# scope.shielded is assigned to in visit_Assign
|
|
46
|
-
|
|
47
|
-
if self.funcname == "CancelScope":
|
|
48
|
-
self.has_timeout = False
|
|
49
|
-
for kw in node.keywords:
|
|
50
|
-
# note: sets to True even if timeout is explicitly set to inf
|
|
51
|
-
if kw.arg == "deadline":
|
|
52
|
-
self.has_timeout = True
|
|
53
43
|
|
|
54
44
|
# trio 0.27 adds shield parameter to all scope helpers
|
|
55
45
|
if self.funcname in cancel_scope_names:
|
|
@@ -79,7 +69,7 @@ class Visitor102(Flake8AsyncVisitor):
|
|
|
79
69
|
self, node: ast.Await | ast.AsyncFor | ast.AsyncWith
|
|
80
70
|
) -> None:
|
|
81
71
|
if self._critical_scope is not None and not any(
|
|
82
|
-
cm.
|
|
72
|
+
cm.shielded for cm in self._trio_context_managers
|
|
83
73
|
):
|
|
84
74
|
# non-critical exception handlers have the statement name set to "except"
|
|
85
75
|
if self._critical_scope.name == "except":
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""4XX error classes, which handle exception groups.
|
|
2
|
+
|
|
3
|
+
ASYNC400 except-star-invalid-attribute checks for invalid attribute access on except*
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from .flake8asyncvisitor import Flake8AsyncVisitor
|
|
12
|
+
from .helpers import error_class
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
|
|
17
|
+
EXCGROUP_ATTRS = (
|
|
18
|
+
# from ExceptionGroup
|
|
19
|
+
"message",
|
|
20
|
+
"exceptions",
|
|
21
|
+
"subgroup",
|
|
22
|
+
"split",
|
|
23
|
+
"derive",
|
|
24
|
+
# from BaseException
|
|
25
|
+
"args",
|
|
26
|
+
"with_traceback",
|
|
27
|
+
"add_note",
|
|
28
|
+
# in the backport
|
|
29
|
+
"_is_protocol",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@error_class
|
|
34
|
+
class Visitor4xx(Flake8AsyncVisitor):
|
|
35
|
+
|
|
36
|
+
error_codes: Mapping[str, str] = {
|
|
37
|
+
"ASYNC400": (
|
|
38
|
+
"Accessing attribute {} on ExceptionGroup as if it was a bare Exception."
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
43
|
+
super().__init__(*args, **kwargs)
|
|
44
|
+
self.exception_groups: list[str] = []
|
|
45
|
+
self.trystar = False
|
|
46
|
+
|
|
47
|
+
def visit_TryStar(self, node: ast.TryStar): # type: ignore[name-defined]
|
|
48
|
+
self.save_state(node, "trystar")
|
|
49
|
+
self.trystar = True
|
|
50
|
+
|
|
51
|
+
def visit_Try(self, node: ast.Try):
|
|
52
|
+
self.save_state(node, "trystar")
|
|
53
|
+
self.trystar = False
|
|
54
|
+
|
|
55
|
+
def visit_ExceptHandler(self, node: ast.ExceptHandler):
|
|
56
|
+
if not self.trystar or node.name is None:
|
|
57
|
+
return
|
|
58
|
+
self.save_state(node, "exception_groups", copy=True)
|
|
59
|
+
self.exception_groups.append(node.name)
|
|
60
|
+
self.visit_nodes(node.body)
|
|
61
|
+
|
|
62
|
+
def visit_Attribute(self, node: ast.Attribute):
|
|
63
|
+
if (
|
|
64
|
+
isinstance(node.value, ast.Name)
|
|
65
|
+
and node.value.id in self.exception_groups
|
|
66
|
+
and node.attr not in EXCGROUP_ATTRS
|
|
67
|
+
and not (node.attr.startswith("__") and node.attr.endswith("__"))
|
|
68
|
+
):
|
|
69
|
+
self.error(node, node.attr)
|
|
70
|
+
|
|
71
|
+
def _clear_if_name(self, node: ast.AST | None):
|
|
72
|
+
if isinstance(node, ast.Name) and node.id in self.exception_groups:
|
|
73
|
+
self.exception_groups.remove(node.id)
|
|
74
|
+
|
|
75
|
+
def _walk_and_clear(self, node: ast.AST | None):
|
|
76
|
+
if node is None:
|
|
77
|
+
return
|
|
78
|
+
for n in ast.walk(node):
|
|
79
|
+
self._clear_if_name(n)
|
|
80
|
+
|
|
81
|
+
def visit_Assign(self, node: ast.Assign):
|
|
82
|
+
for t in node.targets:
|
|
83
|
+
self._walk_and_clear(t)
|
|
84
|
+
|
|
85
|
+
def visit_AnnAssign(self, node: ast.AnnAssign):
|
|
86
|
+
self._clear_if_name(node.target)
|
|
87
|
+
|
|
88
|
+
def visit_withitem(self, node: ast.withitem):
|
|
89
|
+
self._walk_and_clear(node.optional_vars)
|
|
90
|
+
|
|
91
|
+
def visit_FunctionDef(
|
|
92
|
+
self, node: ast.FunctionDef | ast.AsyncFunctionDef | ast.Lambda
|
|
93
|
+
):
|
|
94
|
+
self.save_state(node, "exception_groups", "trystar", copy=False)
|
|
95
|
+
self.exception_groups = []
|
|
96
|
+
|
|
97
|
+
visit_AsyncFunctionDef = visit_FunctionDef
|
|
98
|
+
visit_Lambda = visit_FunctionDef
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import ast
|
|
6
|
+
from collections import defaultdict
|
|
6
7
|
from typing import TYPE_CHECKING, Any, cast
|
|
7
8
|
|
|
8
9
|
from .flake8asyncvisitor import Flake8AsyncVisitor, Flake8AsyncVisitor_cst
|
|
@@ -181,20 +182,25 @@ class Visitor113(Flake8AsyncVisitor):
|
|
|
181
182
|
self.asynccontextmanager = False
|
|
182
183
|
self.aenter = False
|
|
183
184
|
|
|
185
|
+
self.potential_errors: dict[str, list[ast.Call]] = defaultdict(list)
|
|
186
|
+
|
|
184
187
|
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
|
|
185
|
-
self.save_state(node, "aenter")
|
|
188
|
+
self.save_state(node, "aenter", "asynccontextmanager", "potential_errors")
|
|
186
189
|
|
|
187
|
-
self.aenter = node.name == "__aenter__"
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
+
self.aenter = node.name == "__aenter__"
|
|
191
|
+
self.asynccontextmanager = has_decorator(node, "asynccontextmanager")
|
|
190
192
|
|
|
191
193
|
def visit_FunctionDef(self, node: ast.FunctionDef):
|
|
192
|
-
self.save_state(node, "aenter")
|
|
194
|
+
self.save_state(node, "aenter", "asynccontextmanager", "potential_errors")
|
|
193
195
|
# sync function should never be named __aenter__ or have @asynccontextmanager
|
|
194
|
-
self.aenter = False
|
|
196
|
+
self.aenter = self.asynccontextmanager = False
|
|
195
197
|
|
|
196
198
|
def visit_Yield(self, node: ast.Yield):
|
|
197
|
-
self.
|
|
199
|
+
for nodes in self.potential_errors.values():
|
|
200
|
+
for n in nodes:
|
|
201
|
+
self.error(n)
|
|
202
|
+
self.potential_errors.clear()
|
|
203
|
+
self.aenter = self.asynccontextmanager = False
|
|
198
204
|
|
|
199
205
|
def visit_Call(self, node: ast.Call) -> None:
|
|
200
206
|
def is_startable(n: ast.expr, *startable_list: str) -> bool:
|
|
@@ -210,14 +216,14 @@ class Visitor113(Flake8AsyncVisitor):
|
|
|
210
216
|
return any(is_startable(nn, *startable_list) for nn in n.args)
|
|
211
217
|
return False
|
|
212
218
|
|
|
213
|
-
def is_nursery_call(node: ast.expr):
|
|
219
|
+
def is_nursery_call(node: ast.expr) -> str | None:
|
|
214
220
|
if not isinstance(node, ast.Attribute) or node.attr not in (
|
|
215
221
|
"start_soon",
|
|
216
222
|
"create_task",
|
|
217
223
|
):
|
|
218
|
-
return
|
|
224
|
+
return None
|
|
219
225
|
var = ast.unparse(node.value)
|
|
220
|
-
|
|
226
|
+
if (
|
|
221
227
|
("trio" in self.library and var.endswith("nursery"))
|
|
222
228
|
or ("anyio" in self.library and var.endswith("task_group"))
|
|
223
229
|
or (
|
|
@@ -228,11 +234,12 @@ class Visitor113(Flake8AsyncVisitor):
|
|
|
228
234
|
"asyncio.TaskGroup",
|
|
229
235
|
)
|
|
230
236
|
)
|
|
231
|
-
)
|
|
237
|
+
):
|
|
238
|
+
return var
|
|
239
|
+
return None
|
|
232
240
|
|
|
233
241
|
if (
|
|
234
|
-
|
|
235
|
-
and is_nursery_call(node.func)
|
|
242
|
+
(var := is_nursery_call(node.func)) is not None
|
|
236
243
|
and len(node.args) > 0
|
|
237
244
|
and is_startable(
|
|
238
245
|
node.args[0],
|
|
@@ -241,7 +248,24 @@ class Visitor113(Flake8AsyncVisitor):
|
|
|
241
248
|
*self.options.startable_in_context_manager,
|
|
242
249
|
)
|
|
243
250
|
):
|
|
244
|
-
self.
|
|
251
|
+
if self.aenter:
|
|
252
|
+
self.error(node)
|
|
253
|
+
elif self.asynccontextmanager:
|
|
254
|
+
self.potential_errors[var].append(node)
|
|
255
|
+
|
|
256
|
+
def visit_AsyncWith(self, node: ast.AsyncWith | ast.With):
|
|
257
|
+
# Entirely skip any nurseries that doesn't have any yields in them.
|
|
258
|
+
# This fixes an otherwise very thorny false alarm.
|
|
259
|
+
# In the worst case this does mean we iterate over the body twice, but might
|
|
260
|
+
# actually be a performance gain on average due to setting `novisit`
|
|
261
|
+
if not any(isinstance(n, ast.Yield) for b in node.body for n in ast.walk(b)):
|
|
262
|
+
self.novisit = True
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
# open_nursery/create_task_group only works with AsyncWith, but in case somebody
|
|
266
|
+
# is doing something very weird we'll be conservative and possibly avoid
|
|
267
|
+
# some potential false alarms
|
|
268
|
+
visit_With = visit_AsyncWith
|
|
245
269
|
|
|
246
270
|
|
|
247
271
|
# Checks that all async functions with a "task_status" parameter have a match in
|
|
@@ -28,6 +28,7 @@ flake8_async/visitors/visitor111.py
|
|
|
28
28
|
flake8_async/visitors/visitor118.py
|
|
29
29
|
flake8_async/visitors/visitor123.py
|
|
30
30
|
flake8_async/visitors/visitor2xx.py
|
|
31
|
+
flake8_async/visitors/visitor4xx.py
|
|
31
32
|
flake8_async/visitors/visitor91x.py
|
|
32
33
|
flake8_async/visitors/visitor_utility.py
|
|
33
34
|
flake8_async/visitors/visitors.py
|
|
@@ -126,7 +127,9 @@ tests/eval_files/async250_multi_library.py
|
|
|
126
127
|
tests/eval_files/async251.py
|
|
127
128
|
tests/eval_files/async251_multi_library.py
|
|
128
129
|
tests/eval_files/async300.py
|
|
130
|
+
tests/eval_files/async400_py311.py
|
|
129
131
|
tests/eval_files/async900.py
|
|
132
|
+
tests/eval_files/async900_trio.py
|
|
130
133
|
tests/eval_files/async910.py
|
|
131
134
|
tests/eval_files/async910_insert_library.py
|
|
132
135
|
tests/eval_files/async911.py
|
|
@@ -100,8 +100,9 @@ async def foo():
|
|
|
100
100
|
try:
|
|
101
101
|
pass
|
|
102
102
|
finally:
|
|
103
|
+
# it's now fine to have a shield and no timeout
|
|
103
104
|
with trio.CancelScope(shield=True):
|
|
104
|
-
await foo()
|
|
105
|
+
await foo()
|
|
105
106
|
try:
|
|
106
107
|
pass
|
|
107
108
|
finally:
|
|
@@ -24,9 +24,18 @@ async def foo():
|
|
|
24
24
|
# we don't check for `async with`
|
|
25
25
|
with trio.open_nursery() as bar: # type: ignore[attr-defined]
|
|
26
26
|
bar.start_soon(my_startable) # ASYNC113: 8
|
|
27
|
+
yield
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@asynccontextmanager
|
|
31
|
+
async def foo2():
|
|
27
32
|
async with trio.open_nursery() as bar:
|
|
28
33
|
bar.start_soon(my_startable) # ASYNC113: 8
|
|
34
|
+
yield
|
|
35
|
+
|
|
29
36
|
|
|
37
|
+
@asynccontextmanager
|
|
38
|
+
async def foo3():
|
|
30
39
|
boo: trio.Nursery = ... # type: ignore
|
|
31
40
|
boo.start_soon(my_startable) # ASYNC113: 4
|
|
32
41
|
|
|
@@ -132,3 +141,52 @@ async def foo_nested_sync_def():
|
|
|
132
141
|
bar.start_soon(my_startable)
|
|
133
142
|
|
|
134
143
|
yield
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@asynccontextmanager
|
|
147
|
+
async def false_alarm():
|
|
148
|
+
async with trio.open_nursery() as nursery:
|
|
149
|
+
nursery.start_soon(my_startable)
|
|
150
|
+
yield
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@asynccontextmanager
|
|
154
|
+
async def should_error():
|
|
155
|
+
async with trio.open_nursery() as nursery:
|
|
156
|
+
nursery.start_soon(my_startable) # ASYNC113: 8
|
|
157
|
+
# overrides the nursery variable
|
|
158
|
+
async with trio.open_nursery() as nursery:
|
|
159
|
+
nursery.start_soon(my_startable)
|
|
160
|
+
yield
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@asynccontextmanager
|
|
164
|
+
async def foo_sync_with_closed():
|
|
165
|
+
# we don't check for `async with`
|
|
166
|
+
with trio.open_nursery() as bar: # type: ignore[attr-defined]
|
|
167
|
+
bar.start_soon(my_startable)
|
|
168
|
+
yield
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# fixed by entirely skipping nurseries without yields in them
|
|
172
|
+
class FalseAlarm:
|
|
173
|
+
async def __aenter__(self):
|
|
174
|
+
with trio.open_nursery() as nursery:
|
|
175
|
+
nursery.start_soon(my_startable)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@asynccontextmanager
|
|
179
|
+
async def yield_before_start_soon():
|
|
180
|
+
with trio.open_nursery() as bar:
|
|
181
|
+
yield
|
|
182
|
+
bar.start_soon(my_startable)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# This was broken when visit_AsyncWith manually visited subnodes due to not
|
|
186
|
+
# letting TypeTrackerVisitor interject.
|
|
187
|
+
@asynccontextmanager
|
|
188
|
+
async def nested():
|
|
189
|
+
with trio.open_nursery() as foo:
|
|
190
|
+
with trio.open_nursery() as bar:
|
|
191
|
+
bar.start_soon(my_startable) # error: 12
|
|
192
|
+
yield
|
|
@@ -132,8 +132,10 @@ async def foo4(): ...
|
|
|
132
132
|
@asynccontextmanager() # type: ignore[call-arg]
|
|
133
133
|
async def foo_paren():
|
|
134
134
|
nursery.start_soon(trio.run_process) # error: 4
|
|
135
|
+
yield
|
|
135
136
|
|
|
136
137
|
|
|
137
138
|
@asynccontextmanager(1, 2, 3) # type: ignore[call-arg]
|
|
138
139
|
async def foo_params():
|
|
139
140
|
nursery.start_soon(trio.run_process) # error: 4
|
|
141
|
+
yield
|
|
@@ -121,3 +121,13 @@ async def foo_nested_funcdef():
|
|
|
121
121
|
await foo()
|
|
122
122
|
|
|
123
123
|
raise
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# shielded but no timeout no longer triggers async120
|
|
127
|
+
# https://github.com/python-trio/flake8-async/issues/272
|
|
128
|
+
async def foo_shield_no_timeout():
|
|
129
|
+
try:
|
|
130
|
+
...
|
|
131
|
+
finally:
|
|
132
|
+
with trio.CancelScope(shield=True):
|
|
133
|
+
await foo()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
try:
|
|
2
|
+
...
|
|
3
|
+
except* ValueError as e:
|
|
4
|
+
e.anything # error: 4, "anything"
|
|
5
|
+
e.foo() # error: 4, "foo"
|
|
6
|
+
e.bar.zee # error: 4, "bar"
|
|
7
|
+
|
|
8
|
+
# from ExceptionGroup
|
|
9
|
+
e.message
|
|
10
|
+
e.exceptions
|
|
11
|
+
e.subgroup
|
|
12
|
+
e.split
|
|
13
|
+
e.derive
|
|
14
|
+
|
|
15
|
+
# from BaseException
|
|
16
|
+
e.args
|
|
17
|
+
e.with_traceback
|
|
18
|
+
e.add_note
|
|
19
|
+
|
|
20
|
+
# ignore anything that looks like a dunder
|
|
21
|
+
e.__foo__
|
|
22
|
+
e.__bar__
|
|
23
|
+
|
|
24
|
+
e.anything # safe
|
|
25
|
+
|
|
26
|
+
# assigning to the variable clears it
|
|
27
|
+
try:
|
|
28
|
+
...
|
|
29
|
+
except* ValueError as e:
|
|
30
|
+
e = e.exceptions[0]
|
|
31
|
+
e.ignore # safe
|
|
32
|
+
except* ValueError as e:
|
|
33
|
+
e, f = 1, 2
|
|
34
|
+
e.anything # safe
|
|
35
|
+
except* TypeError as e:
|
|
36
|
+
(e, f) = (1, 2)
|
|
37
|
+
e.anything # safe
|
|
38
|
+
except* ValueError as e:
|
|
39
|
+
with blah as e:
|
|
40
|
+
e.anything
|
|
41
|
+
e.anything
|
|
42
|
+
except* ValueError as e:
|
|
43
|
+
e: int = 1
|
|
44
|
+
e.real
|
|
45
|
+
except* ValueError as e:
|
|
46
|
+
with blah as (e, f):
|
|
47
|
+
e.anything
|
|
48
|
+
|
|
49
|
+
# check state saving
|
|
50
|
+
try:
|
|
51
|
+
...
|
|
52
|
+
except* ValueError as e:
|
|
53
|
+
...
|
|
54
|
+
except* BaseException:
|
|
55
|
+
e.error # safe
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
...
|
|
59
|
+
except* ValueError as e:
|
|
60
|
+
try:
|
|
61
|
+
...
|
|
62
|
+
except* TypeError as e:
|
|
63
|
+
...
|
|
64
|
+
e.anything # error: 4, "anything"
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
...
|
|
68
|
+
except* ValueError as e:
|
|
69
|
+
|
|
70
|
+
def foo():
|
|
71
|
+
# possibly problematic, but we minimize false alarms
|
|
72
|
+
e.anything
|
|
73
|
+
|
|
74
|
+
e.anything # error: 4, "anything"
|
|
75
|
+
|
|
76
|
+
def foo(e):
|
|
77
|
+
# this one is more clear it should be treated as safe
|
|
78
|
+
e.anything
|
|
79
|
+
|
|
80
|
+
e.anything # error: 4, "anything"
|
|
81
|
+
|
|
82
|
+
lambda e: e.anything
|
|
83
|
+
|
|
84
|
+
e.anything # error: 4, "anything"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import trio
|
|
2
|
+
|
|
3
|
+
# different error message & no as_safe_channel
|
|
4
|
+
# NOANYIO
|
|
5
|
+
# NOASYNCIO
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
async def foo1(): # ASYNC900: 0, 'trio.as_safe_channel, contextlib.asynccontextmanager, pytest.fixture'
|
|
9
|
+
yield
|
|
10
|
+
yield
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@trio.as_safe_channel
|
|
14
|
+
async def foo():
|
|
15
|
+
yield
|
|
@@ -26,6 +26,10 @@ from hypothesmith import from_grammar, from_node
|
|
|
26
26
|
from flake8_async import Plugin
|
|
27
27
|
from flake8_async.base import Error, Statement
|
|
28
28
|
from flake8_async.visitors import ERROR_CLASSES, ERROR_CLASSES_CST
|
|
29
|
+
from flake8_async.visitors.visitor4xx import EXCGROUP_ATTRS
|
|
30
|
+
|
|
31
|
+
if sys.version_info < (3, 11):
|
|
32
|
+
from exceptiongroup import ExceptionGroup
|
|
29
33
|
|
|
30
34
|
if TYPE_CHECKING:
|
|
31
35
|
from collections.abc import Iterable, Sequence
|
|
@@ -512,6 +516,7 @@ error_codes_ignored_when_checking_transformed_sync_code = {
|
|
|
512
516
|
"ASYNC123",
|
|
513
517
|
"ASYNC125",
|
|
514
518
|
"ASYNC300",
|
|
519
|
+
"ASYNC400",
|
|
515
520
|
"ASYNC912",
|
|
516
521
|
}
|
|
517
522
|
|
|
@@ -845,6 +850,13 @@ def test_910_permutations():
|
|
|
845
850
|
assert not errors, "# false alarm:\n" + function_str
|
|
846
851
|
|
|
847
852
|
|
|
853
|
+
def test_async400_excgroup_attributes():
|
|
854
|
+
for attr in dir(ExceptionGroup):
|
|
855
|
+
if attr.startswith("__") and attr.endswith("__"):
|
|
856
|
+
continue
|
|
857
|
+
assert attr in EXCGROUP_ATTRS
|
|
858
|
+
|
|
859
|
+
|
|
848
860
|
# from https://docs.python.org/3/library/itertools.html#itertools-recipes
|
|
849
861
|
def consume(iterator: Iterable[Any]):
|
|
850
862
|
deque(iterator, maxlen=0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flake8_async-25.4.4 → flake8_async-25.5.2}/tests/eval_files/exception_suppress_context_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|