Skip to content

Commit bae0a63

Browse files
authored
Followup changes to fix ruff & pyright warnings (#203)
* Annotate error_codes with Mapping instead of dict to silence warnings about mutable classvar, Write __hash__ for Statement
1 parent 953a945 commit bae0a63

14 files changed

+86
-56
lines changed

flake8_trio/base.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,22 @@ class Statement(NamedTuple):
2929
lineno: int
3030
col_offset: int = -1
3131

32-
# pyright is unhappy about defining __eq__ but not __hash__ .. which it should
33-
# but it works :tm: and needs changing in a couple places to avoid it.
3432
def __eq__(self, other: object) -> bool:
3533
return (
3634
isinstance(other, Statement)
37-
and self[:2] == other[:2]
35+
and self.name == other.name
36+
and self.lineno == other.lineno
3837
and (
3938
self.col_offset == other.col_offset
4039
or -1 in (self.col_offset, other.col_offset)
4140
)
4241
)
4342

43+
# Objects that are equal needs to have the same hash, so we don't hash on
44+
# `col_offset` since it's a "wildcard" value
45+
def __hash__(self) -> int:
46+
return hash((self.name, self.lineno))
47+
4448

4549
class Error:
4650
def __init__(

flake8_trio/runner.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .visitors.visitor_utility import NoqaHandler
2222

2323
if TYPE_CHECKING:
24-
from collections.abc import Iterable
24+
from collections.abc import Iterable, Mapping
2525

2626
from libcst import Module
2727

@@ -46,7 +46,7 @@ def __init__(self, options: Options):
4646
super().__init__()
4747
self.state = SharedState(options)
4848

49-
def selected(self, error_codes: dict[str, str]) -> bool:
49+
def selected(self, error_codes: Mapping[str, str]) -> bool:
5050
enabled_or_autofix = (
5151
self.state.options.enabled_codes | self.state.options.autofix_codes
5252
)

flake8_trio/visitors/flake8triovisitor.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
import ast
66
from abc import ABC
7-
from typing import TYPE_CHECKING, Any, ClassVar, Union
7+
from typing import TYPE_CHECKING, Any, Union
88

99
import libcst as cst
1010
from libcst.metadata import PositionProvider
1111

1212
from ..base import Error, Statement
1313

1414
if TYPE_CHECKING:
15-
from collections.abc import Iterable
15+
from collections.abc import Iterable, Mapping
1616

1717
from ..runner import SharedState
1818

@@ -23,7 +23,7 @@
2323

2424
class Flake8TrioVisitor(ast.NodeVisitor, ABC):
2525
# abstract attribute by not providing a value
26-
error_codes: ClassVar[dict[str, str]]
26+
error_codes: Mapping[str, str]
2727

2828
def __init__(self, shared_state: SharedState):
2929
super().__init__()
@@ -158,7 +158,7 @@ def add_library(self, name: str) -> None:
158158

159159
class Flake8TrioVisitor_cst(cst.CSTTransformer, ABC):
160160
# abstract attribute by not providing a value
161-
error_codes: dict[str, str]
161+
error_codes: Mapping[str, str]
162162
METADATA_DEPENDENCIES = (PositionProvider,)
163163

164164
def __init__(self, shared_state: SharedState):

flake8_trio/visitors/visitor100.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from __future__ import annotations
1010

11-
from typing import Any
11+
from typing import TYPE_CHECKING, Any
1212

1313
import libcst as cst
1414
import libcst.matchers as m
@@ -21,10 +21,13 @@
2121
with_has_call,
2222
)
2323

24+
if TYPE_CHECKING:
25+
from collections.abc import Mapping
26+
2427

2528
@error_class_cst
2629
class Visitor100_libcst(Flake8TrioVisitor_cst):
27-
error_codes = {
30+
error_codes: Mapping[str, str] = {
2831
"TRIO100": (
2932
"{0}.{1} context contains no checkpoints, remove the context or add"
3033
" `await {0}.lowlevel.checkpoint()`."

flake8_trio/visitors/visitor101.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
)
1818

1919
if TYPE_CHECKING:
20+
from collections.abc import Mapping
21+
2022
import libcst as cst
2123

2224

2325
@error_class_cst
2426
class Visitor101(Flake8TrioVisitor_cst):
25-
error_codes = {
27+
error_codes: Mapping[str, str] = {
2628
"TRIO101": (
2729
"`yield` inside a nursery or cancel scope is only safe when implementing "
2830
"a context manager - otherwise, it breaks exception handling."

flake8_trio/visitors/visitor102.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66
from __future__ import annotations
77

88
import ast
9-
from typing import Any
9+
from typing import TYPE_CHECKING, Any
1010

1111
from ..base import Statement
1212
from .flake8triovisitor import Flake8TrioVisitor
1313
from .helpers import cancel_scope_names, critical_except, error_class, get_matching_call
1414

15+
if TYPE_CHECKING:
16+
from collections.abc import Mapping
17+
1518

1619
@error_class
1720
class Visitor102(Flake8TrioVisitor):
18-
error_codes = {
21+
error_codes: Mapping[str, str] = {
1922
"TRIO102": (
2023
"await inside {0.name} on line {0.lineno} must have shielded cancel "
2124
"scope with a timeout."

flake8_trio/visitors/visitor103_104.py

+14-9
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
from __future__ import annotations
1010

1111
import ast
12-
from typing import Any
12+
from typing import TYPE_CHECKING, Any
1313

1414
from .flake8triovisitor import Flake8TrioVisitor
1515
from .helpers import critical_except, error_class, iter_guaranteed_once
1616

17+
if TYPE_CHECKING:
18+
from collections.abc import Mapping
19+
1720
_trio103_common_msg = "{} block with a code path that doesn't re-raise the error."
1821
_suggestion = " Consider adding an `except {}: raise` before this exception handler."
1922
_suggestion_dict: dict[tuple[str, ...], str] = {
@@ -22,17 +25,19 @@
2225
}
2326
_suggestion_dict[("anyio", "trio")] = "[" + "|".join(_suggestion_dict.values()) + "]"
2427

28+
_error_codes = {
29+
"TRIO103": _trio103_common_msg,
30+
"TRIO104": "Cancelled (and therefore BaseException) must be re-raised.",
31+
}
32+
for poss_library in _suggestion_dict:
33+
_error_codes[f"TRIO103_{'_'.join(poss_library)}"] = (
34+
_trio103_common_msg + _suggestion.format(_suggestion_dict[poss_library])
35+
)
36+
2537

2638
@error_class
2739
class Visitor103_104(Flake8TrioVisitor):
28-
error_codes = {
29-
"TRIO103": _trio103_common_msg,
30-
"TRIO104": "Cancelled (and therefore BaseException) must be re-raised.",
31-
}
32-
for poss_library in _suggestion_dict:
33-
error_codes[f"TRIO103_{'_'.join(poss_library)}"] = (
34-
_trio103_common_msg + _suggestion.format(_suggestion_dict[poss_library])
35-
)
40+
error_codes: Mapping[str, str] = _error_codes
3641

3742
def __init__(self, *args: Any, **kwargs: Any):
3843
super().__init__(*args, **kwargs)

flake8_trio/visitors/visitor105.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
from __future__ import annotations
44

55
import ast
6-
from typing import Any
6+
from typing import TYPE_CHECKING, Any
77

88
from .flake8triovisitor import Flake8TrioVisitor
99
from .helpers import error_class
1010

11+
if TYPE_CHECKING:
12+
from collections.abc import Mapping
13+
14+
1115
# used in 105
1216
trio_async_funcs = (
1317
"trio.aclose_forcefully",
@@ -39,7 +43,7 @@
3943

4044
@error_class
4145
class Visitor105(Flake8TrioVisitor):
42-
error_codes = {
46+
error_codes: Mapping[str, str] = {
4347
"TRIO105": "{0} async {1} must be immediately awaited.",
4448
}
4549

flake8_trio/visitors/visitor111.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
from __future__ import annotations
44

55
import ast
6-
from typing import Any, NamedTuple
6+
from typing import TYPE_CHECKING, Any, NamedTuple
77

88
from .flake8triovisitor import Flake8TrioVisitor
99
from .helpers import error_class, get_matching_call
1010

11+
if TYPE_CHECKING:
12+
from collections.abc import Mapping
13+
1114

1215
@error_class
1316
class Visitor111(Flake8TrioVisitor):
14-
error_codes = {
17+
error_codes: Mapping[str, str] = {
1518
"TRIO111": (
1619
"variable {2} is usable within the context manager on line {0}, but that "
1720
"will close before nursery opened on line {1} - this is usually a bug. "

flake8_trio/visitors/visitor118.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88

99
import ast
1010
import re
11+
from typing import TYPE_CHECKING
1112

1213
from .flake8triovisitor import Flake8TrioVisitor
1314
from .helpers import error_class
1415

16+
if TYPE_CHECKING:
17+
from collections.abc import Mapping
18+
1519

1620
@error_class
1721
class Visitor118(Flake8TrioVisitor):
18-
error_codes = {
22+
error_codes: Mapping[str, str] = {
1923
"TRIO118": (
2024
"Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable,"
2125
" since that breaks linter checks and multi-backend programs."

flake8_trio/visitors/visitor2xx.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@
1212

1313
import ast
1414
import re
15-
from typing import Any
15+
from typing import TYPE_CHECKING, Any
1616

1717
from .flake8triovisitor import Flake8TrioVisitor
1818
from .helpers import error_class, fnmatch_qualified_name, get_matching_call
1919

20+
if TYPE_CHECKING:
21+
from collections.abc import Mapping
22+
2023

2124
@error_class
2225
class Visitor200(Flake8TrioVisitor):
23-
error_codes = {
26+
error_codes: Mapping[str, str] = {
2427
"TRIO200": (
2528
"User-configured blocking sync call {0} in async function, consider "
2629
"replacing with {1}."
@@ -55,7 +58,7 @@ def visit_blocking_call(self, node: ast.Call):
5558

5659
@error_class
5760
class Visitor21X(Visitor200):
58-
error_codes = {
61+
error_codes: Mapping[str, str] = {
5962
"TRIO210": "Sync HTTP call {} in async function, use `httpx.AsyncClient`.",
6063
"TRIO211": (
6164
"Likely sync HTTP call {} in async function, use `httpx.AsyncClient`."
@@ -114,7 +117,7 @@ def visit_blocking_call(self, node: ast.Call):
114117

115118
@error_class
116119
class Visitor212(Visitor200):
117-
error_codes = {
120+
error_codes: Mapping[str, str] = {
118121
"TRIO212": (
119122
"Blocking sync HTTP call {1} on httpx object {0}, use httpx.AsyncClient."
120123
)
@@ -166,7 +169,7 @@ def visit_blocking_call(self, node: ast.Call):
166169
# Process invocations 202
167170
@error_class
168171
class Visitor22X(Visitor200):
169-
error_codes = {
172+
error_codes: Mapping[str, str] = {
170173
"TRIO220": (
171174
"Sync call {} in async function, use "
172175
"`await nursery.start({}.run_process, ...)`."
@@ -225,7 +228,7 @@ def is_p_wait(arg: ast.expr) -> bool:
225228

226229
@error_class
227230
class Visitor23X(Visitor200):
228-
error_codes = {
231+
error_codes: Mapping[str, str] = {
229232
"TRIO230": "Sync call {0} in async function, use `{1}.open_file(...)`.",
230233
"TRIO231": "Sync call {0} in async function, use `{1}.wrap_file({0})`.",
231234
}
@@ -251,7 +254,7 @@ def visit_blocking_call(self, node: ast.Call):
251254

252255
@error_class
253256
class Visitor232(Visitor200):
254-
error_codes = {
257+
error_codes: Mapping[str, str] = {
255258
"TRIO232": (
256259
"Blocking sync call {1} on file object {0}, wrap the file object"
257260
"in `{2}.wrap_file()` to get an async file object."
@@ -281,7 +284,7 @@ def visit_blocking_call(self, node: ast.Call):
281284

282285
@error_class
283286
class Visitor24X(Visitor200):
284-
error_codes = {
287+
error_codes: Mapping[str, str] = {
285288
"TRIO240": "Avoid using os.path, prefer using {1}.Path objects.",
286289
}
287290

flake8_trio/visitors/visitor91x.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626
)
2727

2828
if TYPE_CHECKING:
29-
from collections.abc import Sequence
29+
from collections.abc import Mapping, Sequence
3030

3131

32+
# Statement injected at the start of loops to track missed checkpoints.
3233
ARTIFICIAL_STATEMENT = Statement("artificial", -1)
3334

3435

@@ -226,7 +227,7 @@ def leave_Yield(
226227
@error_class_cst
227228
@disabled_by_default
228229
class Visitor91X(Flake8TrioVisitor_cst, CommonVisitors):
229-
error_codes = {
230+
error_codes: Mapping[str, str] = {
230231
"TRIO910": (
231232
"{0} from async function with no guaranteed checkpoint or exception "
232233
"since function definition on line {1.lineno}."
@@ -591,10 +592,7 @@ def visit_While_body(self, node: cst.For | cst.While):
591592
if getattr(node, "asynchronous", None):
592593
self.uncheckpointed_statements = set()
593594
else:
594-
# pyright correctly dislikes Statement defining __eq__ but not __hash__
595-
# but it works:tm:, and changing it touches on various bits of code, so
596-
# leaving it for another time.
597-
self.uncheckpointed_statements = {ARTIFICIAL_STATEMENT} # pyright: ignore
595+
self.uncheckpointed_statements = {ARTIFICIAL_STATEMENT}
598596

599597
self.loop_state.uncheckpointed_before_continue = set()
600598
self.loop_state.uncheckpointed_before_break = set()

0 commit comments

Comments
 (0)