10000 clean up, comments, and deindent test statements that needn't be insi… · python-trio/flake8-async@e590e76 · GitHub
[go: up one dir, main page]

Skip to content

Commit e590e76

Browse files
committed
clean up, comments, and deindent test statements that needn't be inside async function
1 parent a300295 commit e590e76

File tree

5 files changed

+313
-301
lines changed

5 files changed

+313
-301
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
*[CalVer, YY.month.patch](https://calver.org/)*
33

44
## Future
5-
- Added TRIO302: async context manager inside nursery. Nurseries should be outermost.
6-
- add TRIO112, nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call.
5+
- Add TRIO111: async context manager inside nursery. Nurseries should be outermost.
6+
- Add TRIO112: nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call.
77

88
## 22.8.4
99
- Fix TRIO108 raising errors on yields in some sync code.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ pip install flake8-trio
33 10000 33
Checkpoints are `await`, `async for`, and `async with` (on one of enter/exit).
3434
- **TRIO109**: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead
3535
- **TRIO110**: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.
36-
- **TRIO302**: async context manager inside nursery. Nurseries should be outermost.
36+
- **TRIO111**: Variable from context manager opened inside nursery passed to `start[_soon]` might get closed while in use.
3737
- **TRIO112**: nursery body with only a call to `nursery.start[_soon]` and not passing itself as a parameter can be replaced with a regular function call.

flake8_trio.py

Lines changed: 82 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@
1111

1212
import ast
1313
import tokenize
14-
<<<<<<< HEAD
15-
from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Set, Tuple, Union
16-
=======
1714
from typing import (
1815
Any,
1916
Dict,
@@ -26,7 +23,6 @@
2623
Union,
2724
cast,
2825
)
29-
>>>>>>> trio112_single_statement_nursery
3026

3127
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
3228
__version__ = "22.8.4"
@@ -59,11 +55,11 @@
5955
"`trio.[fail/move_on]_[after/at]` instead"
6056
),
6157
"TRIO110": "`while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.",
62-
<<<<<<< HEAD
63-
"TRIO302": "variable {2}, from context manager on line {0}, passed to {3} from nursery opened on {1}, might get closed while in use",
64-
=======
58+
"TRIO111": (
59+
"variable {2}, from context manager on line {0}, "
60+
"passed to {3} from nursery opened on {1}, might get closed while in use"
61+
),
6562
"TRIO112": "Redundant nursery {}, consider replacing with a regular function call",
66-
>>>>>>> trio112_single_statement_nursery
6763
}
6864

6965

@@ -182,7 +178,6 @@ def get_state(self, *attrs: str, copy: bool = False) -> Dict[str, Any]:
182178
value = value.copy()
183179
res[attr] = value
184180
return res
185-
# return {attr: getattr(self, attr) for attr in attrs if attr != "_problems"}
186181

187182
def set_state(self, attrs: Dict[str, Any], copy: bool = False):
188183
for attr, value in attrs.items():
@@ -195,21 +190,6 @@ def walk(self, *body: ast.AST) -> Iterable[ast.AST]:
195190
yield from ast.walk(b)
196191

197192

198-
<<<<<<< HEAD
199-
def get_trio_scope(node: ast.AST, *names: str) -> Optional[TrioScope]:
200-
if (
201-
isinstance(node, ast.Call)
202-
and isinstance(node.func, ast.Attribute)
203-
and isinstance(node.func.value, ast.Name)
204-
and node.func.value.id == "trio"
205-
and (node.func.attr in names or not names)
206-
):
207-
return TrioScope(node, node.func.attr)
208-
return None
209-
210-
211-
=======
212-
>>>>>>> trio112_single_statement_nursery
213193
def has_decorator(decorator_list: List[ast.expr], *names: str):
214194
for dec in decorator_list:
215195
if (isinstance(dec, ast.Name) and dec.id in names) or (
@@ -219,57 +199,68 @@ def has_decorator(decorator_list: List[ast.expr], *names: str):
219199
return False
220200

221201

222-
# handles 100, 101, 106, 109, 110
202+
# handles 100, 101, 106, 109, 110, 111, 112
223203
class VisitorMiscChecks(Flake8TrioVisitor):
204+
class NurseryCall(NamedTuple):
205+
stack_index: int
206+
name: str
207+
208+
class TrioContextManager(NamedTuple):
209+
lineno: int
210+
name: str
211+
is_nursery: bool
212+
224213
def __init__(self):
225214
super().__init__()
226215

227216
# 101
228217
self._yield_is_error = False
229218
self._safe_decorator = False
230219

231-
# 302
232-
self._context_manager_stack: List[Tuple[ast.expr, str, bool]] = []
233-
self._nursery_call_index: Optional[int] = None
234-
self._nursery_call_name: Optional[str] = None
220+
# 111
221+
self._context_managers: List[VisitorMiscChecks.TrioContextManager] = []
222+
self._nursery_call: Optional[VisitorMiscChecks.NurseryCall] = None
235223

236-
self.defaults = self.get_state()
224+
self.defaults = self.get_state(copy=True)
237225

238-
# ---- 100, 101, 302 ----
226+
# ---- 100, 101, 111, 112 ----
239227
def visit_With(self, node: Union[ast.With, ast.AsyncWith]):
240228
self.check_for_trio100(node)
241229
self.check_for_trio112(node)
242230

243-
outer = self.get_state("_yield_is_error", "_context_manager_stack", copy=True)
231+
outer = self.get_state("_yield_is_error", "_context_managers", copy=True)
244232

245-
# Check for a `with trio.<scope_creater>`
246233
for item in node.items:
247234
# 101
248-
if not self._safe_decorator and not self._yield_is_error:
249-
if (
250-
<<<<<<< HEAD
251-
get_trio_scope(
252-
item.context_expr, "open_nursery", *cancel_scope_names
253-
)
254-
=======
255-
get_matching_call(item, "open_nursery", *cancel_scope_names)
256-
>>>>>>> trio112_single_statement_nursery
257-
is not None
258-
):
259-
self._yield_is_error = True
260-
# 302
261-
if isinstance(item.optional_vars, ast.Name) and isinstance(
262-
item.context_expr, ast.Call
235+
# if there's no safe decorator,
236+
# and it's not yet been determined that yield is error
237+
# and this withitem opens a cancelscope:
238+
# then yielding is unsafe
239+
if (
240+
not self._safe_decorator
241+
and not self._yield_is_error
242+
and get_matching_call(
243+
item.context_expr, "open_nursery", *cancel_scope_names
244+
)
245+
is not None
263246
):
264-
is_nursery = (
265-
get_trio_scope(item.context_expr, "open_nursery") is not None
247+
self._yield_is_error = True
248+
249+
# 111
250+
# if a withitem is saved in a variable,
251+
# push its line, variable, and whether it's a trio nursery
252+
# to the _context_managers stack,
253+
if isinstance(item.optional_vars, ast.Name):
254+
self._context_managers.append(
255+
self.TrioContextManager(
256+
item.context_expr.lineno,
257+
item.optional_vars.id,
258+
get_matching_call(item.context_expr, "open_nursery")
259+
is not None,
260+
)
266261
)
267-
poop = (item.context_expr.func, item.optional_vars.id, is_nursery)
268-
self._context_manager_stack.append(poop)
269262

270263
self.generic_visit(node)
271-
272-
# reset yield_is_error
273264
self.set_state(outer)
274265

275266
visit_AsyncWith = visit_With
@@ -318,8 +309,11 @@ def visit_Yield(self, node: ast.Yield):
318309

319310
# ---- 109 ----
320311
def check_for_trio109(self, node: ast.AsyncFunctionDef):
312+
# pending configuration or a more sophisticated check, ignore
313+
# all functions with a decorator
321314
if node.decorator_list:
322315
return
316+
323317
args = node.args
324318
for arg in (*args.posonlyargs, *args.args, *args.kwonlyargs):
325319
if arg.arg == "timeout":
@@ -351,41 +345,53 @@ def check_for_trio110(self, node: ast.While):
351345
):
352346
self.error("TRIO110", node)
353347

354-
<<<<<<< HEAD
348+
# ---- 111 ----
349+
# if it's a <X>.start[_soon] call
350+
# and <X> is a nursery listed in self._context_managers:
351+
# Save <X>'s index in self._context_managers to guard against cm's higher in the
352+
# stack being passed as parameters to it. (and save <X> for the error message)
355353
def visit_Call(self, node: ast.Call):
356-
outer = self.get_state("_nursery_call_index", "_nursery_call_name")
354+
outer = self.get_state("_nursery_call")
357355

358356
if (
359357
isinstance(node.func, ast.Attribute)
360358
and isinstance(node.func.value, ast.Name)
361359
and node.func.attr in ("start", "start_soon")
362360
):
363-
self._nursery_call_index = None
364-
for i, (_, cm_name, is_nursery) in enumerate(self._context_manager_stack):
365-
if node.func.value.id == cm_name:
366-
if is_nursery:
367-
self._nursery_call_index = i
368-
self._nursery_call_name = node.func.attr
361+
self._nursery_call = None
362+
for i, cm in enumerate(self._context_managers):
363+
if node.func.value.id == cm.name:
364+
# don't break upon finding a nursery in case there's multiple cm's
365+
# on the stack with the same name
366+
if cm.is_nursery:
367+
self._nursery_call = self.Nurser 2851 yCall(i, node.func.attr)
369368
else:
370-
self._nursery_call_index = self._nursery_call_name = None
369+
self._nursery_call = None
371370

372371
self.generic_visit(node)
373372
self.set_state(outer)
374373

F438 374+
# If we're inside a <X>.start[_soon] call (where <X> is a nursery),
375+
# and we're accessing a variable cm that's on the self._context_managers stack,
376+
# with a higher index than <X>:
377+
# Raise error since the scope of cm may close before the function passed to the
378+
# nursery finishes.
375379
def visit_Name(self, node: ast.Name):
376-
if self._nursery_call_index is not None:
377-
for i, (expr, cm_name, _) in enumerate(self._context_manager_stack):
378-
if cm_name == node.id and i > self._nursery_call_index:
379-
self.error(
380-
"TRIO302",
381-
node,
382-
expr.lineno,
383-
self._context_manager_stack[self._nursery_call_index][0].lineno,
384-
node.id,
385-
self._nursery_call_name,
386-
)
387380
self.generic_visit(node)
388-
=======
381+
if self._nursery_call is None:
382+
return
383+
384+
for i, cm in enumerate(self._context_managers):
385+
if cm.name == node.id and i > self._nursery_call.stack_index:
386+
self.error(
387+
"TRIO111",
388+
node,
389+
cm.lineno,
390+
self._context_managers[self._nursery_call.stack_index].lineno,
391+
node.id,
392+
self._nursery_call.name,
393+
)
394+
389395
# if with has a withitem `trio.open_nursery() as <X>`,
390396
# and the body is only a single expression <X>.start[_soon](),
391397
# and does not pass <X> as a parameter to the expression
@@ -415,9 +421,9 @@ def check_for_trio112(self, node: Union[ast.With, ast.AsyncWith]):
415421
)
416422
):
417423
self.error("TRIO112", item.context_expr, var_name)
418-
>>>>>>> trio112_single_statement_nursery
419424

420425

426+
# used in 102, 103 and 104
421427
def critical_except(node: ast.ExceptHandler) -> Optional[Statement]:
422428
def has_exception(node: Optional[ast.expr]) -> str:
423429
if isinstance(node, ast.Name) and node.id == "BaseException":

0 commit comments

Comments
 (0)
0