14
14
from typing import Any , Dict , Iterable , List , Optional , Tuple , Type , Union
15
15
16
16
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
17
+ < << << << HEAD
17
18
__version__ = "22.7.6"
19
+ == == == =
20
+ __version__ = "22.8.1"
21
+ > >> >> >> origin / main
18
22
19
23
Error = Tuple [int , int , str , Type [Any ]]
20
24
@@ -40,14 +44,14 @@ def make_error(error: str, lineno: int, col: int, *args: Any, **kwargs: Any) ->
40
44
class Flake8TrioVisitor (ast .NodeVisitor ):
41
45
def __init__ (self ):
42
46
super ().__init__ ()
43
- self .problems : List [Error ] = []
47
+ self ._problems : List [Error ] = []
44
48
self .suppress_errors = False
45
49
46
50
@classmethod
47
51
def run (cls , tree : ast .AST ) -> Iterable [Error ]:
48
52
visitor = cls ()
49
53
visitor .visit (tree )
50
- yield from visitor .problems
54
+ yield from visitor ._problems
51
55
52
56
def visit_nodes (
53
57
self , * nodes : Union [ast .AST , Iterable [ast .AST ]], generic : bool = False
@@ -65,12 +69,12 @@ def visit_nodes(
65
69
66
70
def error (self , error : str , lineno : int , col : int , * args : Any , ** kwargs : Any ):
67
71
if not self .suppress_errors :
68
- self .problems .append (make_error (error , lineno , col , * args , ** kwargs ))
72
+ self ._problems .append (make_error (error , lineno , col , * args , ** kwargs ))
69
73
70
74
def get_state (self , * attrs : str ) -> Dict [str , Any ]:
71
75
if not attrs :
72
76
attrs = tuple (self .__dict__ .keys ())
73
- return {attr : getattr (self , attr ) for attr in attrs if attr != "problems " }
77
+ return {attr : getattr (self , attr ) for attr in attrs if attr != "_problems " }
74
78
75
79
def set_state (self , attrs : Dict [str , Any ]):
76
80
for attr , value in attrs .items ():
@@ -158,8 +162,9 @@ def visit_AsyncWith(self, node: ast.AsyncWith):
158
162
self .visit_With (node )
159
163
160
164
def visit_FunctionDef (self , node : Union [ast .FunctionDef , ast .AsyncFunctionDef ]):
161
- outer = self .get_state ("_safe_decorator" , "_yield_is_error" )
165
+ outer = self .get_state ()
162
166
self ._yield_is_error = False
167
+ self ._inside_loop = False
163
168
164
169
# check for @<context_manager_name> and @<library>.<context_manager_name>
165
170
if has_decorator (node .decorator_list , * context_manager_names ):
@@ -170,11 +175,12 @@ def visit_FunctionDef(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef]):
170
175
self .set_state (outer )
171
176
172
177
def visit_AsyncFunctionDef (self , node : ast .AsyncFunctionDef ):
178
+ self .check_109 (node .args )
173
179
self .visit_FunctionDef (node )
174
180
175
181
def visit_Yield (self , node : ast .Yield ):
176
182
if self ._yield_is_error :
177
- self .problems . append ( make_error ( TRIO101 , node .lineno , node .col_offset ) )
183
+ self .error ( TRIO101 , node .lineno , node .col_offset )
178
184
179
185
self .generic_visit (node )
180
186
@@ -186,19 +192,35 @@ def check_for_trio100(self, node: Union[ast.With, ast.AsyncWith]):
186
192
isinstance (x , checkpoint_node_types ) and x != node
187
193
for x in ast .walk (node )
188
194
):
189
- self .problems .append (
190
- make_error (TRIO100 , item .lineno , item .col_offset , call )
191
- )
195
+ self .error (TRIO100 , item .lineno , item .col_offset , call )
192
196
193
197
def visit_ImportFrom (self , node : ast .ImportFrom ):
194
198
if node .module == "trio" :
195
- self .problems . append ( make_error ( TRIO106 , node .lineno , node .col_offset ) )
199
+ self .error ( TRIO106 , node .lineno , node .col_offset )
196
200
self .generic_visit (node )
197
201
198
202
def visit_Import (self , node : ast .Import ):
199
203
for name in node .names :
200
204
if name .name == "trio" and name .asname is not None :
201
- self .problems .append (make_error (TRIO106 , node .lineno , node .col_offset ))
205
+ self .error (TRIO106 , node .lineno , node .col_offset )
206
+
207
+ def check_109 (self , args : ast .arguments ):
208
+ for arg in (* args .posonlyargs , * args .args , * args .kwonlyargs ):
209
+ if arg .arg == "timeout" :
210
+ self .error (TRIO109 , arg .lineno , arg .col_offset )
211
+
212
+ def visit_While (self , node : ast .While ):
213
+ self .check_for_110 (node )
214
+ self .generic_visit (node )
215
+
216
+ def check_for_110 (self , node : ast .While ):
217
+ if (
218
+ len (node .body ) == 1
219
+ and isinstance (node .body [0 ], ast .Expr )
220
+ and isinstance (node .body [0 ].value , ast .Await )
221
+ and get_trio_scope (node .body [0 ].value .value , "sleep" , "sleep_until" )
222
+ ):
223
+ self .error (TRIO110 , node .lineno , node .col_offset )
202
224
203
225
204
226
def critical_except (node : ast .ExceptHandler ) -> Optional [Tuple [int , int , str ]]:
@@ -252,9 +274,7 @@ def visit_Await(
252
274
cm .has_timeout and cm .shielded for cm in self ._trio_context_managers
253
275
)
254
276
):
255
- self .problems .append (
256
- make_error (TRIO102 , node .lineno , node .col_offset , * self ._critical_scope )
257
- )
277
+ self .error (TRIO102 , node .lineno , node .col_offset , * self ._critical_scope )
258
278
if visit_children :
259
279
self .generic_visit (node )
260
280
@@ -288,14 +308,15 @@ def visit_AsyncWith(self, node: ast.AsyncWith):
288
308
self .visit_With (node )
289
309
290
310
def visit_FunctionDef (self , node : Union [ast .FunctionDef , ast .AsyncFunctionDef ]):
291
- outer_cm = self ._safe_decorator
311
+ outer = self .get_state ( " _safe_decorator" )
292
312
293
313
# check for @<context_manager_name> and @<library>.<context_manager_name>
294
314
if has_decorator (node .decorator_list , * context_manager_names ):
295
315
self ._safe_decorator = True
296
316
297
317
self .generic_visit (node )
298
- self ._safe_decorator = outer_cm
318
+
319
+ self .set_state (outer )
299
320
300
321
visit_AsyncFunctionDef = visit_FunctionDef
301
322
@@ -305,13 +326,13 @@ def critical_visit(
305
326
block : Tuple [int , int , str ],
306
327
generic : bool = False ,
307
328
):
308
- outer = self ._critical_scope , self . _trio_context_managers
329
+ outer = self .get_state ( " _critical_scope" , " _trio_context_managers" )
309
330
310
331
self ._trio_context_managers = []
311
332
self ._critical_scope = block
312
333
313
334
self .visit_nodes (node , generic = generic )
314
- self ._critical_scope , self . _trio_context_managers = outer
335
+ self .set_state ( outer )
315
336
316
337
def visit_Try (self , node : ast .Try ):
317
338
# There's no visit_Finally, so we need to manually visit the Try fields.
@@ -358,7 +379,7 @@ def __init__(self):
358
379
# then there might be a code path that doesn't re-raise.
359
380
def visit_ExceptHandler (self , node : ast .ExceptHandler ):
360
381
361
- outer = self .get_state ("unraised" , "except_name" , "loop_depth" )
382
+ outer = self .get_state ()
362
383
marker = critical_except (node )
363
384
364
385
# we need to *not* unset self.unraised if this is non-critical, to still
@@ -375,7 +396,7 @@ def visit_ExceptHandler(self, node: ast.ExceptHandler):
375
396
self .generic_visit (node )
376
397
377
398
if self .unraised and marker is not None :
378
- self .problems . append ( make_error ( TRIO103 , * marker ) )
399
+ self .error ( TRIO103 , * marker )
379
400
380
401
self .set_state (outer )
381
402
@@ -387,7 +408,7 @@ def visit_Raise(self, node: ast.Raise):
387
408
and node .exc is not None
388
409
and not (isinstance (node .exc , ast .Name ) and node .exc .id == self .except_name )
389
410
):
390
- self .problems . append ( make_error ( TRIO104 , node .lineno , node .col_offset ) )
411
+ self .error ( TRIO104 , node .lineno , node .col_offset )
391
412
392
413
# treat it as safe regardless, to avoid unnecessary error messages.
393
414
self .unraised = False
@@ -397,7 +418,7 @@ def visit_Raise(self, node: ast.Raise):
397
418
def visit_Return (self , node : Union [ast .Return , ast .Yield ]):
398
419
if self .unraised :
399
420
# Error: must re-raise
400
- self .problems . append ( make_error ( TRIO104 , node .lineno , node .col_offset ) )
421
+ self .error ( TRIO104 , node .lineno , node .col_offset )
401
422
self .generic_visit (node )
402
423
403
424
visit_Yield = visit_Return
@@ -446,7 +467,7 @@ def visit_If(self, node: ast.If):
446
467
# we completely disregard them when checking coverage by resetting the
447
468
# effects of them afterwards
448
469
def visit_For (self , node : Union [ast .For , ast .While ]):
449
- outer_unraised = self .unraised
470
+ outer = self .get_state ( " unraised" )
450
471
451
472
self .loop_depth += 1
452
473
for n in node .body :
@@ -455,13 +476,13 @@ def visit_For(self, node: Union[ast.For, ast.While]):
455
476
for n in node .orelse :
456
477
self .visit (n )
457
478
458
- self .unraised = outer_unraised
479
+ self .set_state ( outer )
459
480
460
481
visit_While = visit_For
461
482
462
483
def visit_Break (self , node : Union [ast .Break , ast .Continue ]):
463
484
if self .unraised and self .loop_depth == 0 :
464
- self .problems . append ( make_error ( TRIO104 , node .lineno , node .col_offset ) )
485
+ self .error ( TRIO104 , node .lineno , node .col_offset )
465
486
self .generic_visit (node )
466
487
467
488
visit_Continue = visit_Break
@@ -506,9 +527,7 @@ def visit_Call(self, node: ast.Call):
506
527
or not isinstance (self .node_stack [- 2 ], ast .Await )
507
528
)
508
529
):
509
- self .problems .append (
510
- make_error (TRIO105 , node .lineno , node .col_offset , node .func .attr )
511
- )
530
+ self .error (TRIO105 , node .lineno , node .col_offset , node .func .attr )
512
531
self .generic_visit (node )
513
532
514
533
@@ -779,3 +798,7 @@ def run(self) -> Iterable[Error]:
779
798
TRIO108 = (
780
799
"TRIO108: {} from async function with no guaranteed checkpoint since {} on line {}"
781
800
)
801
+ #TRIO107 = "TRIO107: Async functions must have at least one checkpoint on every code path, unless an exception is raised"
802
+ #TRIO108 = "TRIO108: Early return from async function must have at least one checkpoint on every code path before it."
803
+ TRIO109 = "TRIO109: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead"
804
+ TRIO110 = "TRIO110: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`."
0 commit comments