flake8-async 25.4.2__tar.gz → 25.4.4__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.2 → flake8_async-25.4.4}/PKG-INFO +1 -1
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/__init__.py +1 -1
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/runner.py +5 -5
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/helpers.py +28 -13
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor91x.py +138 -63
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitors.py +8 -5
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async.egg-info/PKG-INFO +1 -1
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async.egg-info/SOURCES.txt +1 -1
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async100.py +32 -0
- flake8_async-25.4.4/tests/autofix_files/async100_asyncio.py +31 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async100.py +38 -0
- flake8_async-25.4.4/tests/eval_files/async100_asyncio.py +32 -0
- flake8_async-25.4.4/tests/eval_files/async100_asyncio_noautofix.py +21 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async900.py +6 -3
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async912_asyncio.py +6 -17
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tox.ini +3 -3
- flake8_async-25.4.2/tests/autofix_files/async100_asyncio.py +0 -22
- flake8_async-25.4.2/tests/eval_files/async100_asyncio.py +0 -22
- flake8_async-25.4.2/tests/eval_files/async100_noautofix.py +0 -25
- {flake8_async-25.4.2 → flake8_async-25.4.4}/CHANGELOG.md +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/CONTRIBUTING.md +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/LICENSE +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/MANIFEST.in +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/README.md +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/__main__.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/base.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/__init__.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/flake8asyncvisitor.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor101.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor102_120.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor103_104.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor105.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor111.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor118.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor123.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor2xx.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async/visitors/visitor_utility.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async.egg-info/dependency_links.txt +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async.egg-info/entry_points.txt +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async.egg-info/not-zip-safe +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async.egg-info/requires.txt +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/flake8_async.egg-info/top_level.txt +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/pyproject.toml +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/setup.cfg +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/setup.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/__init__.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async100_simple_autofix.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async124.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async910.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async910_insert_library.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async911.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async911_insert_library.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async913.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async913_trio_anyio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async91x_autofix.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async91x_py310.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/async91x_py311.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/exception_suppress_context_manager.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/exception_suppress_context_manager_import_star.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/noqa.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/autofix_files/noqa_testing.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/check_changelog_and_version.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/conftest.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/anyio_trio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async100_simple_autofix.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async101.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async101_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async102.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async102_120_py311.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async102_aclose.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async102_aclose_args.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async102_anyio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async102_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async102_trio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async103.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async103_104_py310.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async103_104_py311.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async103_all_imported.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async103_both_imported.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async103_no_104.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async103_trio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async104.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async104_anyio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async104_trio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async105.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async105_anyio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async106.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async109.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async110.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async111.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async111_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async112.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async112_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async113.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async113_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async113_trio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async114.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async115.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async116.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async118.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async119.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async120.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async121.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async121_anyio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async121_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async122.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async123.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async123_py311.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async124.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async124_no_autofix.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async125.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async200.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async210.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async211.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async212.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async22x.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async22x_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async232.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async232_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async23x.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async23x_asyncio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async240.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async250.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async250_multi_library.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async251.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async251_multi_library.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async300.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async910.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async910_insert_library.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async911.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async911_insert_library.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async912.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async912_py311.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async913.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async913_trio_anyio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async91x_autofix.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async91x_noautofix.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async91x_py310.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/async91x_py311.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/exception_suppress_context_manager.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/exception_suppress_context_manager_import_star.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/no_library.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/noqa.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/noqa_no_autofix.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/noqa_testing.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/eval_files/trio_anyio.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/test_all_visitors_imported.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/test_config_and_args.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/test_decorator.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/test_exception_on_invalid_code.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/test_flake8_async.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/test_formatting.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/test_messages_documented.py +0 -0
- {flake8_async-25.4.2 → flake8_async-25.4.4}/tests/trio_options.py +0 -0
|
@@ -12,6 +12,7 @@ from typing import TYPE_CHECKING
|
|
|
12
12
|
|
|
13
13
|
import libcst as cst
|
|
14
14
|
|
|
15
|
+
from .base import Error, Options
|
|
15
16
|
from .visitors import (
|
|
16
17
|
ERROR_CLASSES,
|
|
17
18
|
ERROR_CLASSES_CST,
|
|
@@ -25,18 +26,17 @@ if TYPE_CHECKING:
|
|
|
25
26
|
|
|
26
27
|
from libcst import Module
|
|
27
28
|
|
|
28
|
-
from .base import Error, Options
|
|
29
29
|
from .visitors.flake8asyncvisitor import Flake8AsyncVisitor, Flake8AsyncVisitor_cst
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
@dataclass
|
|
33
33
|
class SharedState:
|
|
34
34
|
options: Options
|
|
35
|
-
problems: list[Error] = field(default_factory=list)
|
|
36
|
-
noqas: dict[int, set[str]] = field(default_factory=dict)
|
|
35
|
+
problems: list[Error] = field(default_factory=list[Error])
|
|
36
|
+
noqas: dict[int, set[str]] = field(default_factory=dict[int, set[str]])
|
|
37
37
|
library: tuple[str, ...] = ()
|
|
38
|
-
typed_calls: dict[str, str] = field(default_factory=dict)
|
|
39
|
-
variables: dict[str, str] = field(default_factory=dict)
|
|
38
|
+
typed_calls: dict[str, str] = field(default_factory=dict[str, str])
|
|
39
|
+
variables: dict[str, str] = field(default_factory=dict[str, str])
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class __CommonRunner:
|
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
import ast
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
from fnmatch import fnmatch
|
|
11
|
-
from typing import TYPE_CHECKING,
|
|
11
|
+
from typing import TYPE_CHECKING, Generic, TypeVar, Union
|
|
12
12
|
|
|
13
13
|
import libcst as cst
|
|
14
14
|
import libcst.matchers as m
|
|
@@ -38,6 +38,8 @@ if TYPE_CHECKING:
|
|
|
38
38
|
"T_EITHER", bound=Union[Flake8AsyncVisitor, Flake8AsyncVisitor_cst]
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
T_Call = TypeVar("T_Call", bound=Union[cst.Call, ast.Call])
|
|
42
|
+
|
|
41
43
|
|
|
42
44
|
def error_class(error_class: type[T]) -> type[T]:
|
|
43
45
|
assert error_class.error_codes
|
|
@@ -289,8 +291,8 @@ cancel_scope_names = (
|
|
|
289
291
|
|
|
290
292
|
|
|
291
293
|
@dataclass
|
|
292
|
-
class MatchingCall:
|
|
293
|
-
node:
|
|
294
|
+
class MatchingCall(Generic[T_Call]):
|
|
295
|
+
node: T_Call
|
|
294
296
|
name: str
|
|
295
297
|
base: str
|
|
296
298
|
|
|
@@ -301,7 +303,7 @@ class MatchingCall:
|
|
|
301
303
|
# convenience function used in a lot of visitors
|
|
302
304
|
def get_matching_call(
|
|
303
305
|
node: ast.AST, *names: str, base: Iterable[str] = ("trio", "anyio")
|
|
304
|
-
) -> MatchingCall | None:
|
|
306
|
+
) -> MatchingCall[ast.Call] | None:
|
|
305
307
|
if isinstance(base, str):
|
|
306
308
|
base = (base,)
|
|
307
309
|
if (
|
|
@@ -316,6 +318,23 @@ def get_matching_call(
|
|
|
316
318
|
|
|
317
319
|
|
|
318
320
|
# ___ CST helpers ___
|
|
321
|
+
def get_matching_call_cst(
|
|
322
|
+
node: cst.CSTNode, *names: str, base: Iterable[str] = ("trio", "anyio")
|
|
323
|
+
) -> MatchingCall[cst.Call] | None:
|
|
324
|
+
if isinstance(base, str):
|
|
325
|
+
base = (base,)
|
|
326
|
+
if (
|
|
327
|
+
isinstance(node, cst.Call)
|
|
328
|
+
and isinstance(node.func, cst.Attribute)
|
|
329
|
+
and node.func.attr.value in names
|
|
330
|
+
and isinstance(node.func.value, (cst.Name, cst.Attribute))
|
|
331
|
+
):
|
|
332
|
+
attr_base = identifier_to_string(node.func.value)
|
|
333
|
+
if attr_base is not None and attr_base in base:
|
|
334
|
+
return MatchingCall(node, node.func.attr.value, attr_base)
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
|
|
319
338
|
def oneof_names(*names: str):
|
|
320
339
|
return m.OneOf(*map(m.Name, names))
|
|
321
340
|
|
|
@@ -329,12 +348,6 @@ def list_contains(
|
|
|
329
348
|
yield from (item for item in seq if m.matches(item, matcher))
|
|
330
349
|
|
|
331
350
|
|
|
332
|
-
class AttributeCall(NamedTuple):
|
|
333
|
-
node: cst.Call
|
|
334
|
-
base: str
|
|
335
|
-
function: str
|
|
336
|
-
|
|
337
|
-
|
|
338
351
|
# the custom __or__ in libcst breaks pyright type checking. It's possible to use
|
|
339
352
|
# `Union` as a workaround ... except pyupgrade will automatically replace that.
|
|
340
353
|
# So we have to resort to specifying one of the base classes.
|
|
@@ -365,7 +378,7 @@ def identifier_to_string(node: cst.CSTNode) -> str | None:
|
|
|
365
378
|
|
|
366
379
|
def with_has_call(
|
|
367
380
|
node: cst.With, *names: str, base: Iterable[str] | str = ("trio", "anyio")
|
|
368
|
-
) -> list[
|
|
381
|
+
) -> list[MatchingCall[cst.Call]]:
|
|
369
382
|
"""Check if a with statement has a matching call, returning a list with matches.
|
|
370
383
|
|
|
371
384
|
`names` specify the names of functions to match, `base` specifies the
|
|
@@ -396,7 +409,7 @@ def with_has_call(
|
|
|
396
409
|
)
|
|
397
410
|
)
|
|
398
411
|
|
|
399
|
-
res_list: list[
|
|
412
|
+
res_list: list[MatchingCall[cst.Call]] = []
|
|
400
413
|
for item in node.items:
|
|
401
414
|
if res := m.extract(item.item, matcher):
|
|
402
415
|
assert isinstance(item.item, cst.Call)
|
|
@@ -405,7 +418,9 @@ def with_has_call(
|
|
|
405
418
|
base_string = identifier_to_string(res["base"])
|
|
406
419
|
assert base_string is not None, "subscripts should never get matched"
|
|
407
420
|
res_list.append(
|
|
408
|
-
|
|
421
|
+
MatchingCall(
|
|
422
|
+
node=item.item, base=base_string, name=res["function"].value
|
|
423
|
+
)
|
|
409
424
|
)
|
|
410
425
|
return res_list
|
|
411
426
|
|
|
@@ -19,21 +19,21 @@ from typing import TYPE_CHECKING, Any, cast
|
|
|
19
19
|
|
|
20
20
|
import libcst as cst
|
|
21
21
|
import libcst.matchers as m
|
|
22
|
-
from libcst.metadata import PositionProvider
|
|
22
|
+
from libcst.metadata import CodeRange, PositionProvider
|
|
23
23
|
|
|
24
24
|
from ..base import Statement
|
|
25
25
|
from .flake8asyncvisitor import Flake8AsyncVisitor_cst
|
|
26
26
|
from .helpers import (
|
|
27
|
-
|
|
27
|
+
MatchingCall,
|
|
28
28
|
cancel_scope_names,
|
|
29
29
|
disable_codes_by_default,
|
|
30
30
|
error_class_cst,
|
|
31
31
|
flatten_preserving_comments,
|
|
32
32
|
fnmatch_qualified_name_cst,
|
|
33
33
|
func_has_decorator,
|
|
34
|
+
get_matching_call_cst,
|
|
34
35
|
identifier_to_string,
|
|
35
36
|
iter_guaranteed_once_cst,
|
|
36
|
-
with_has_call,
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
if TYPE_CHECKING:
|
|
@@ -162,13 +162,21 @@ class LoopState:
|
|
|
162
162
|
body_guaranteed_once: bool = False
|
|
163
163
|
has_break: bool = False
|
|
164
164
|
|
|
165
|
-
uncheckpointed_before_continue: set[Statement] = field(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
artificial_errors: set[cst.Return | cst.Yield] = field(default_factory=set)
|
|
169
|
-
nodes_needing_checkpoints: list[cst.Return | cst.Yield | ArtificialStatement] = (
|
|
170
|
-
field(default_factory=list)
|
|
165
|
+
uncheckpointed_before_continue: set[Statement] = field(
|
|
166
|
+
default_factory=set[Statement]
|
|
171
167
|
)
|
|
168
|
+
uncheckpointed_before_break: set[Statement] = field(default_factory=set[Statement])
|
|
169
|
+
# pyright emits reportUnknownVariableType, requiring the generic to default_factory
|
|
170
|
+
# to be specified.
|
|
171
|
+
# But for these we require a union, and `|` doesn't work on py39, and uses of
|
|
172
|
+
# `Union` gets autofixed by ruff.
|
|
173
|
+
# So.... let's just ignore the error for now
|
|
174
|
+
artificial_errors: set[ # pyright: ignore[reportUnknownVariableType]
|
|
175
|
+
cst.Return | cst.Yield
|
|
176
|
+
] = field(default_factory=set)
|
|
177
|
+
nodes_needing_checkpoints: list[ # pyright: ignore[reportUnknownVariableType]
|
|
178
|
+
cst.Return | cst.Yield | ArtificialStatement
|
|
179
|
+
] = field(default_factory=list)
|
|
172
180
|
|
|
173
181
|
def copy(self):
|
|
174
182
|
return LoopState(
|
|
@@ -184,10 +192,14 @@ class LoopState:
|
|
|
184
192
|
|
|
185
193
|
@dataclass
|
|
186
194
|
class TryState:
|
|
187
|
-
body_uncheckpointed_statements: set[Statement] = field(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
body_uncheckpointed_statements: set[Statement] = field(
|
|
196
|
+
default_factory=set[Statement]
|
|
197
|
+
)
|
|
198
|
+
try_checkpoint: set[Statement] = field(default_factory=set[Statement])
|
|
199
|
+
except_uncheckpointed_statements: set[Statement] = field(
|
|
200
|
+
default_factory=set[Statement]
|
|
201
|
+
)
|
|
202
|
+
added: set[Statement] = field(default_factory=set[Statement])
|
|
191
203
|
|
|
192
204
|
def copy(self):
|
|
193
205
|
return TryState(
|
|
@@ -202,8 +214,12 @@ class TryState:
|
|
|
202
214
|
class MatchState:
|
|
203
215
|
# TryState, LoopState, and MatchState all do fairly similar things. It would be nice
|
|
204
216
|
# to harmonize them and share logic.
|
|
205
|
-
base_uncheckpointed_statements: set[Statement] = field(
|
|
206
|
-
|
|
217
|
+
base_uncheckpointed_statements: set[Statement] = field(
|
|
218
|
+
default_factory=set[Statement]
|
|
219
|
+
)
|
|
220
|
+
case_uncheckpointed_statements: set[Statement] = field(
|
|
221
|
+
default_factory=set[Statement]
|
|
222
|
+
)
|
|
207
223
|
has_fallback: bool = False
|
|
208
224
|
|
|
209
225
|
def copy(self):
|
|
@@ -358,6 +374,14 @@ class InsertCheckpointsInLoopBody(CommonVisitors):
|
|
|
358
374
|
disable_codes_by_default("ASYNC910", "ASYNC911", "ASYNC912", "ASYNC913")
|
|
359
375
|
|
|
360
376
|
|
|
377
|
+
@dataclass
|
|
378
|
+
class ContextManager:
|
|
379
|
+
has_checkpoint: bool | None = None
|
|
380
|
+
call: MatchingCall[cst.Call] | None = None
|
|
381
|
+
line: int | None = None
|
|
382
|
+
column: int | None = None
|
|
383
|
+
|
|
384
|
+
|
|
361
385
|
@error_class_cst
|
|
362
386
|
class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
363
387
|
error_codes: Mapping[str, str] = {
|
|
@@ -392,8 +416,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
392
416
|
self.match_state = MatchState()
|
|
393
417
|
|
|
394
418
|
# ASYNC100
|
|
395
|
-
self.has_checkpoint_stack: list[
|
|
396
|
-
self.node_dict: dict[cst.With, list[AttributeCall]] = {}
|
|
419
|
+
self.has_checkpoint_stack: list[ContextManager] = []
|
|
397
420
|
self.taskgroup_has_start_soon: dict[str, bool] = {}
|
|
398
421
|
|
|
399
422
|
# --exception-suppress-context-manager
|
|
@@ -413,7 +436,11 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
413
436
|
)
|
|
414
437
|
|
|
415
438
|
def checkpoint_cancel_point(self) -> None:
|
|
416
|
-
|
|
439
|
+
for cm in reversed(self.has_checkpoint_stack):
|
|
440
|
+
if cm.has_checkpoint:
|
|
441
|
+
# Everything further down in the stack is already True.
|
|
442
|
+
break
|
|
443
|
+
cm.has_checkpoint = True
|
|
417
444
|
# don't need to look for any .start_soon() calls
|
|
418
445
|
self.taskgroup_has_start_soon.clear()
|
|
419
446
|
|
|
@@ -689,59 +716,106 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
689
716
|
# missing-checkpoint warning when there might in fact be one (i.e. a false alarm).
|
|
690
717
|
def visit_With_body(self, node: cst.With):
|
|
691
718
|
self.save_state(node, "taskgroup_has_start_soon", copy=True)
|
|
692
|
-
|
|
719
|
+
|
|
720
|
+
is_suppressing = False
|
|
693
721
|
|
|
694
722
|
# if this might suppress exceptions, we cannot treat anything inside it as
|
|
695
723
|
# checkpointing.
|
|
696
724
|
if self._is_exception_suppressing_context_manager(node):
|
|
697
725
|
self.save_state(node, "uncheckpointed_statements", copy=True)
|
|
698
726
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
727
|
+
for withitem in node.items:
|
|
728
|
+
self.has_checkpoint_stack.append(ContextManager())
|
|
729
|
+
if get_matching_call_cst(
|
|
730
|
+
withitem.item, "open_nursery", "create_task_group"
|
|
731
|
+
):
|
|
732
|
+
if withitem.asname is not None and isinstance(
|
|
733
|
+
withitem.asname.name, cst.Name
|
|
734
|
+
):
|
|
735
|
+
self.taskgroup_has_start_soon[withitem.asname.name.value] = False
|
|
736
|
+
self.checkpoint_schedule_point()
|
|
737
|
+
# Technically somebody could set open_nursery or create_task_group as
|
|
738
|
+
# suppressing context managers, but we're not add logic for that.
|
|
739
|
+
continue
|
|
740
|
+
|
|
741
|
+
if bool(getattr(node, "asynchronous", False)):
|
|
742
|
+
self.checkpoint()
|
|
743
|
+
|
|
744
|
+
# not a clean function call
|
|
745
|
+
if not isinstance(withitem.item, cst.Call) or not isinstance(
|
|
746
|
+
withitem.item.func, (cst.Name, cst.Attribute)
|
|
747
|
+
):
|
|
748
|
+
continue
|
|
749
|
+
|
|
750
|
+
if (
|
|
751
|
+
fnmatch_qualified_name_cst(
|
|
752
|
+
(withitem.item.func,),
|
|
753
|
+
"contextlib.suppress",
|
|
754
|
+
*self.suppress_imported_as,
|
|
755
|
+
*self.options.exception_suppress_context_managers,
|
|
756
|
+
)
|
|
757
|
+
is not None
|
|
758
|
+
):
|
|
759
|
+
# Don't re-update state if there's several suppressing cm's.
|
|
760
|
+
if not is_suppressing:
|
|
761
|
+
self.save_state(node, "uncheckpointed_statements", copy=True)
|
|
762
|
+
is_suppressing = True
|
|
763
|
+
continue
|
|
764
|
+
|
|
765
|
+
if res := (
|
|
766
|
+
get_matching_call_cst(withitem.item, *cancel_scope_names)
|
|
767
|
+
or get_matching_call_cst(
|
|
768
|
+
withitem.item,
|
|
769
|
+
"timeout",
|
|
770
|
+
"timeout_at",
|
|
771
|
+
base="asyncio",
|
|
772
|
+
)
|
|
773
|
+
):
|
|
774
|
+
# typing issue: https://github.com/Instagram/LibCST/issues/1107
|
|
775
|
+
pos = cst.ensure_type(
|
|
776
|
+
self.get_metadata(PositionProvider, withitem),
|
|
777
|
+
CodeRange,
|
|
778
|
+
).start
|
|
779
|
+
self.uncheckpointed_statements.add(
|
|
780
|
+
ArtificialStatement("withitem", pos.line, pos.column)
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
cm = self.has_checkpoint_stack[-1]
|
|
784
|
+
cm.line = pos.line
|
|
785
|
+
cm.column = pos.column
|
|
786
|
+
cm.call = res
|
|
787
|
+
cm.has_checkpoint = False
|
|
715
788
|
|
|
716
789
|
def leave_With(self, original_node: cst.With, updated_node: cst.With):
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
790
|
+
withitems = list(updated_node.items)
|
|
791
|
+
for i in reversed(range(len(updated_node.items))):
|
|
792
|
+
cm = self.has_checkpoint_stack.pop()
|
|
793
|
+
# ASYNC100
|
|
794
|
+
if cm.has_checkpoint is False:
|
|
795
|
+
res = cm.call
|
|
796
|
+
assert res is not None
|
|
723
797
|
# bypass 910 & 911's should_autofix logic, which excludes asyncio
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
798
|
+
if self.error(
|
|
799
|
+
res.node, res.base, res.name, error_code="ASYNC100"
|
|
800
|
+
) and super().should_autofix(res.node, code="ASYNC100"):
|
|
801
|
+
if len(withitems) == 1:
|
|
802
|
+
# Remove this With node, bypassing later logic.
|
|
803
|
+
return flatten_preserving_comments(updated_node)
|
|
804
|
+
if i == len(withitems) - 1:
|
|
805
|
+
# preserve trailing comma, or remove comma if there was none
|
|
806
|
+
withitems[-2] = withitems[-2].with_changes(
|
|
807
|
+
comma=withitems[-1].comma
|
|
808
|
+
)
|
|
809
|
+
withitems.pop(i)
|
|
810
|
+
|
|
811
|
+
# ASYNC912
|
|
812
|
+
elif cm.call is not None:
|
|
813
|
+
assert cm.line is not None
|
|
814
|
+
assert cm.column is not None
|
|
815
|
+
s = ArtificialStatement("withitem", cm.line, cm.column)
|
|
816
|
+
if s in self.uncheckpointed_statements:
|
|
817
|
+
self.uncheckpointed_statements.remove(s)
|
|
818
|
+
self.error(cm.call.node, error_code="ASYNC912")
|
|
745
819
|
|
|
746
820
|
# if exception-suppressing, restore all uncheckpointed statements from
|
|
747
821
|
# before the `with`.
|
|
@@ -751,7 +825,8 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
|
|
|
751
825
|
self.uncheckpointed_statements.update(prev_checkpoints)
|
|
752
826
|
|
|
753
827
|
self._checkpoint_with(original_node, entry=False)
|
|
754
|
-
|
|
828
|
+
|
|
829
|
+
return updated_node.with_changes(items=withitems)
|
|
755
830
|
|
|
756
831
|
# error if no checkpoint since earlier yield or function entry
|
|
757
832
|
def leave_Yield(
|
|
@@ -555,18 +555,21 @@ class Visitor900(Flake8AsyncVisitor):
|
|
|
555
555
|
def __init__(self, *args: Any, **kwargs: Any):
|
|
556
556
|
super().__init__(*args, **kwargs)
|
|
557
557
|
self.unsafe_function: ast.AsyncFunctionDef | None = None
|
|
558
|
-
self.transform_decorators =
|
|
559
|
-
"asynccontextmanager",
|
|
560
|
-
"fixture",
|
|
558
|
+
self.transform_decorators = [
|
|
559
|
+
"contextlib.asynccontextmanager",
|
|
560
|
+
"pytest.fixture",
|
|
561
561
|
*self.options.transform_async_generator_decorators,
|
|
562
|
-
|
|
562
|
+
]
|
|
563
|
+
# only recommend if using trio
|
|
564
|
+
if "trio" in self.library:
|
|
565
|
+
self.transform_decorators.insert(0, "trio.as_safe_channel")
|
|
563
566
|
|
|
564
567
|
def visit_AsyncFunctionDef(
|
|
565
568
|
self, node: ast.AsyncFunctionDef | ast.FunctionDef | ast.Lambda
|
|
566
569
|
):
|
|
567
570
|
self.save_state(node, "unsafe_function")
|
|
568
571
|
if isinstance(node, ast.AsyncFunctionDef) and not has_decorator(
|
|
569
|
-
node, *self.transform_decorators
|
|
572
|
+
node, *(d.split(".")[-1] for d in self.transform_decorators)
|
|
570
573
|
):
|
|
571
574
|
self.unsafe_function = node
|
|
572
575
|
else:
|
|
@@ -62,7 +62,7 @@ tests/autofix_files/noqa_testing.py
|
|
|
62
62
|
tests/eval_files/anyio_trio.py
|
|
63
63
|
tests/eval_files/async100.py
|
|
64
64
|
tests/eval_files/async100_asyncio.py
|
|
65
|
-
tests/eval_files/
|
|
65
|
+
tests/eval_files/async100_asyncio_noautofix.py
|
|
66
66
|
tests/eval_files/async100_simple_autofix.py
|
|
67
67
|
tests/eval_files/async101.py
|
|
68
68
|
tests/eval_files/async101_asyncio.py
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
# AUTOFIX
|
|
3
3
|
# ASYNCIO_NO_ERROR # timeout primitives are named differently in asyncio
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import trio
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
def condition() -> bool:
|
|
10
|
+
return False
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
# error: 5, "trio", "move_on_after"
|
|
8
14
|
...
|
|
9
15
|
|
|
@@ -214,3 +220,29 @@ async def nursery_exit_blocks_with_start():
|
|
|
214
220
|
async with trio.open_nursery() as n:
|
|
215
221
|
with trio.CancelScope():
|
|
216
222
|
await n.start(trio.sleep, 0)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
async def autofix_multi_withitem():
|
|
226
|
+
with open("foo"): # error: 9, "trio", "CancelScope"
|
|
227
|
+
...
|
|
228
|
+
|
|
229
|
+
# this one is completely removed
|
|
230
|
+
# error: 8, "trio", "CancelScope"
|
|
231
|
+
# error: 8, "trio", "CancelScope"
|
|
232
|
+
...
|
|
233
|
+
|
|
234
|
+
# these keep the `open`
|
|
235
|
+
with (
|
|
236
|
+
open("aa") as _, # error: 8, "trio", "fail_after"
|
|
237
|
+
):
|
|
238
|
+
...
|
|
239
|
+
|
|
240
|
+
with (
|
|
241
|
+
open("bb") as _, # error: 8, "trio", "move_on_after"
|
|
242
|
+
):
|
|
243
|
+
...
|
|
244
|
+
|
|
245
|
+
with (
|
|
246
|
+
open("cc") as f,
|
|
247
|
+
):
|
|
248
|
+
...
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# TRIO_NO_ERROR
|
|
2
|
+
# ANYIO_NO_ERROR
|
|
3
|
+
# BASE_LIBRARY asyncio
|
|
4
|
+
|
|
5
|
+
# asyncio.timeout[_at] added in py3.11
|
|
6
|
+
# mypy: disable-error-code=attr-defined
|
|
7
|
+
# AUTOFIX
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def foo():
|
|
13
|
+
# error: 9, "asyncio", "timeout_at"
|
|
14
|
+
...
|
|
15
|
+
# error: 9, "asyncio", "timeout"
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# this is technically only a problem with asyncio, since timeout primitives in trio/anyio
|
|
20
|
+
# are sync cm's
|
|
21
|
+
async def multi_withitem():
|
|
22
|
+
with open("foo"): # error: 9, "asyncio", "timeout"
|
|
23
|
+
...
|
|
24
|
+
with open("foo"): # error: 22, "asyncio", "timeout"
|
|
25
|
+
...
|
|
26
|
+
# retain explicit trailing comma (?)
|
|
27
|
+
with (
|
|
28
|
+
open("foo"),
|
|
29
|
+
open("bar"), # error: 8, "asyncio", "timeout"
|
|
30
|
+
):
|
|
31
|
+
...
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
# AUTOFIX
|
|
3
3
|
# ASYNCIO_NO_ERROR # timeout primitives are named differently in asyncio
|
|
4
4
|
|
|
5
|
+
import contextlib
|
|
5
6
|
import trio
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
def condition() -> bool:
|
|
10
|
+
return False
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
with trio.move_on_after(10): # error: 5, "trio", "move_on_after"
|
|
8
14
|
...
|
|
9
15
|
|
|
@@ -214,3 +220,35 @@ async def nursery_exit_blocks_with_start():
|
|
|
214
220
|
async with trio.open_nursery() as n:
|
|
215
221
|
with trio.CancelScope():
|
|
216
222
|
await n.start(trio.sleep, 0)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
async def autofix_multi_withitem():
|
|
226
|
+
with trio.CancelScope(), open("foo"): # error: 9, "trio", "CancelScope"
|
|
227
|
+
...
|
|
228
|
+
|
|
229
|
+
# this one is completely removed
|
|
230
|
+
with (
|
|
231
|
+
trio.CancelScope(), # error: 8, "trio", "CancelScope"
|
|
232
|
+
trio.CancelScope(), # error: 8, "trio", "CancelScope"
|
|
233
|
+
):
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
# these keep the `open`
|
|
237
|
+
with (
|
|
238
|
+
open("aa") as _,
|
|
239
|
+
trio.fail_after(10), # error: 8, "trio", "fail_after"
|
|
240
|
+
):
|
|
241
|
+
...
|
|
242
|
+
|
|
243
|
+
with (
|
|
244
|
+
trio.fail_after(5), # error: 8, "trio", "fail_after"
|
|
245
|
+
open("bb") as _,
|
|
246
|
+
trio.move_on_after(5), # error: 8, "trio", "move_on_after"
|
|
247
|
+
):
|
|
248
|
+
...
|
|
249
|
+
|
|
250
|
+
with (
|
|
251
|
+
trio.move_on_after(10), # error: 8, "trio", "move_on_after"
|
|
252
|
+
open("cc") as f,
|
|
253
|
+
):
|
|
254
|
+
...
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# TRIO_NO_ERROR
|
|
2
|
+
# ANYIO_NO_ERROR
|
|
3
|
+
# BASE_LIBRARY asyncio
|
|
4
|
+
|
|
5
|
+
# asyncio.timeout[_at] added in py3.11
|
|
6
|
+
# mypy: disable-error-code=attr-defined
|
|
7
|
+
# AUTOFIX
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def foo():
|
|
13
|
+
with asyncio.timeout_at(10): # error: 9, "asyncio", "timeout_at"
|
|
14
|
+
...
|
|
15
|
+
with asyncio.timeout(10): # error: 9, "asyncio", "timeout"
|
|
16
|
+
...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# this is technically only a problem with asyncio, since timeout primitives in trio/anyio
|
|
20
|
+
# are sync cm's
|
|
21
|
+
async def multi_withitem():
|
|
22
|
+
with asyncio.timeout(10), open("foo"): # error: 9, "asyncio", "timeout"
|
|
23
|
+
...
|
|
24
|
+
with open("foo"), asyncio.timeout(10): # error: 22, "asyncio", "timeout"
|
|
25
|
+
...
|
|
26
|
+
# retain explicit trailing comma (?)
|
|
27
|
+
with (
|
|
28
|
+
open("foo"),
|
|
29
|
+
open("bar"),
|
|
30
|
+
asyncio.timeout(10), # error: 8, "asyncio", "timeout"
|
|
31
|
+
):
|
|
32
|
+
...
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# TRIO_NO_ERROR
|
|
2
|
+
# ANYIO_NO_ERROR
|
|
3
|
+
# BASE_LIBRARY asyncio
|
|
4
|
+
|
|
5
|
+
# We remove the last timeout, but don't re-evaluate the whole with statement,
|
|
6
|
+
# so the test still raises an error.
|
|
7
|
+
# NOAUTOFIX
|
|
8
|
+
|
|
9
|
+
# asyncio.timeout[_at] added in py3.11
|
|
10
|
+
# mypy: disable-error-code=attr-defined
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def multi_withitem():
|
|
16
|
+
async with asyncio.timeout(
|
|
17
|
+
10
|
|
18
|
+
), asyncio.timeout_at( # error: 7, "asyncio", "timeout_at"
|
|
19
|
+
10
|
|
20
|
+
):
|
|
21
|
+
...
|