flake8-async 25.3.1__tar.gz → 25.4.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.
Files changed (151) hide show
  1. {flake8_async-25.3.1 → flake8_async-25.4.2}/PKG-INFO +2 -2
  2. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/__init__.py +1 -1
  3. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/helpers.py +13 -3
  4. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor102_120.py +2 -2
  5. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor103_104.py +18 -0
  6. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor91x.py +67 -1
  7. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor_utility.py +1 -1
  8. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitors.py +45 -4
  9. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async.egg-info/PKG-INFO +2 -2
  10. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async.egg-info/SOURCES.txt +4 -0
  11. flake8_async-25.4.2/tests/autofix_files/async91x_py310.py +90 -0
  12. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/check_changelog_and_version.py +3 -1
  13. flake8_async-25.4.2/tests/eval_files/async103_104_py310.py +58 -0
  14. flake8_async-25.4.2/tests/eval_files/async125.py +33 -0
  15. flake8_async-25.4.2/tests/eval_files/async91x_py310.py +86 -0
  16. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/test_flake8_async.py +1 -0
  17. {flake8_async-25.3.1 → flake8_async-25.4.2}/CHANGELOG.md +0 -0
  18. {flake8_async-25.3.1 → flake8_async-25.4.2}/CONTRIBUTING.md +0 -0
  19. {flake8_async-25.3.1 → flake8_async-25.4.2}/LICENSE +0 -0
  20. {flake8_async-25.3.1 → flake8_async-25.4.2}/MANIFEST.in +0 -0
  21. {flake8_async-25.3.1 → flake8_async-25.4.2}/README.md +0 -0
  22. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/__main__.py +0 -0
  23. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/base.py +0 -0
  24. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/runner.py +0 -0
  25. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/__init__.py +0 -0
  26. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/flake8asyncvisitor.py +0 -0
  27. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor101.py +0 -0
  28. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor105.py +0 -0
  29. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor111.py +0 -0
  30. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor118.py +0 -0
  31. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor123.py +0 -0
  32. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async/visitors/visitor2xx.py +0 -0
  33. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async.egg-info/dependency_links.txt +0 -0
  34. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async.egg-info/entry_points.txt +0 -0
  35. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async.egg-info/not-zip-safe +0 -0
  36. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async.egg-info/requires.txt +0 -0
  37. {flake8_async-25.3.1 → flake8_async-25.4.2}/flake8_async.egg-info/top_level.txt +0 -0
  38. {flake8_async-25.3.1 → flake8_async-25.4.2}/pyproject.toml +0 -0
  39. {flake8_async-25.3.1 → flake8_async-25.4.2}/setup.cfg +0 -0
  40. {flake8_async-25.3.1 → flake8_async-25.4.2}/setup.py +0 -0
  41. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/__init__.py +0 -0
  42. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async100.py +0 -0
  43. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async100_asyncio.py +0 -0
  44. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async100_simple_autofix.py +0 -0
  45. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async124.py +0 -0
  46. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async910.py +0 -0
  47. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async910_insert_library.py +0 -0
  48. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async911.py +0 -0
  49. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async911_insert_library.py +0 -0
  50. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async913.py +0 -0
  51. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async913_trio_anyio.py +0 -0
  52. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async91x_autofix.py +0 -0
  53. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/async91x_py311.py +0 -0
  54. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/exception_suppress_context_manager.py +0 -0
  55. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/exception_suppress_context_manager_import_star.py +0 -0
  56. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/noqa.py +0 -0
  57. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/autofix_files/noqa_testing.py +0 -0
  58. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/conftest.py +0 -0
  59. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/anyio_trio.py +0 -0
  60. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async100.py +0 -0
  61. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async100_asyncio.py +0 -0
  62. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async100_noautofix.py +0 -0
  63. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async100_simple_autofix.py +0 -0
  64. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async101.py +0 -0
  65. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async101_asyncio.py +0 -0
  66. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async102.py +0 -0
  67. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async102_120_py311.py +0 -0
  68. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async102_aclose.py +0 -0
  69. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async102_aclose_args.py +0 -0
  70. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async102_anyio.py +0 -0
  71. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async102_asyncio.py +0 -0
  72. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async102_trio.py +0 -0
  73. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async103.py +0 -0
  74. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async103_104_py311.py +0 -0
  75. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async103_all_imported.py +0 -0
  76. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async103_both_imported.py +0 -0
  77. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async103_no_104.py +0 -0
  78. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async103_trio.py +0 -0
  79. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async104.py +0 -0
  80. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async104_anyio.py +0 -0
  81. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async104_trio.py +0 -0
  82. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async105.py +0 -0
  83. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async105_anyio.py +0 -0
  84. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async106.py +0 -0
  85. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async109.py +0 -0
  86. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async110.py +0 -0
  87. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async111.py +0 -0
  88. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async111_asyncio.py +0 -0
  89. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async112.py +0 -0
  90. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async112_asyncio.py +0 -0
  91. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async113.py +0 -0
  92. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async113_asyncio.py +0 -0
  93. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async113_trio.py +0 -0
  94. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async114.py +0 -0
  95. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async115.py +0 -0
  96. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async116.py +0 -0
  97. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async118.py +0 -0
  98. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async119.py +0 -0
  99. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async120.py +0 -0
  100. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async121.py +0 -0
  101. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async121_anyio.py +0 -0
  102. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async121_asyncio.py +0 -0
  103. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async122.py +0 -0
  104. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async123.py +0 -0
  105. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async123_py311.py +0 -0
  106. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async124.py +0 -0
  107. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async124_no_autofix.py +0 -0
  108. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async200.py +0 -0
  109. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async210.py +0 -0
  110. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async211.py +0 -0
  111. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async212.py +0 -0
  112. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async22x.py +0 -0
  113. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async22x_asyncio.py +0 -0
  114. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async232.py +0 -0
  115. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async232_asyncio.py +0 -0
  116. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async23x.py +0 -0
  117. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async23x_asyncio.py +0 -0
  118. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async240.py +0 -0
  119. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async250.py +0 -0
  120. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async250_multi_library.py +0 -0
  121. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async251.py +0 -0
  122. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async251_multi_library.py +0 -0
  123. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async300.py +0 -0
  124. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async900.py +0 -0
  125. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async910.py +0 -0
  126. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async910_insert_library.py +0 -0
  127. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async911.py +0 -0
  128. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async911_insert_library.py +0 -0
  129. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async912.py +0 -0
  130. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async912_asyncio.py +0 -0
  131. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async912_py311.py +0 -0
  132. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async913.py +0 -0
  133. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async913_trio_anyio.py +0 -0
  134. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async91x_autofix.py +0 -0
  135. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async91x_noautofix.py +0 -0
  136. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/async91x_py311.py +0 -0
  137. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/exception_suppress_context_manager.py +0 -0
  138. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/exception_suppress_context_manager_import_star.py +0 -0
  139. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/no_library.py +0 -0
  140. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/noqa.py +0 -0
  141. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/noqa_no_autofix.py +0 -0
  142. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/noqa_testing.py +0 -0
  143. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/eval_files/trio_anyio.py +0 -0
  144. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/test_all_visitors_imported.py +0 -0
  145. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/test_config_and_args.py +0 -0
  146. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/test_decorator.py +0 -0
  147. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/test_exception_on_invalid_code.py +0 -0
  148. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/test_formatting.py +0 -0
  149. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/test_messages_documented.py +0 -0
  150. {flake8_async-25.3.1 → flake8_async-25.4.2}/tests/trio_options.py +0 -0
  151. {flake8_async-25.3.1 → flake8_async-25.4.2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: flake8-async
3
- Version: 25.3.1
3
+ Version: 25.4.2
4
4
  Summary: A highly opinionated flake8 plugin for Trio-related problems.
5
5
  Author: Zac Hatfield-Dodds, John Litborn, and Contributors
6
6
  Author-email: zac@zhd.dev
@@ -38,7 +38,7 @@ if TYPE_CHECKING:
38
38
 
39
39
 
40
40
  # CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
41
- __version__ = "25.3.1"
41
+ __version__ = "25.4.2"
42
42
 
43
43
 
44
44
  # taken from https://github.com/Zac-HD/shed
@@ -6,6 +6,7 @@ Also contains the decorator definitions used to register error classes.
6
6
  from __future__ import annotations
7
7
 
8
8
  import ast
9
+ from dataclasses import dataclass
9
10
  from fnmatch import fnmatch
10
11
  from typing import TYPE_CHECKING, NamedTuple, TypeVar, Union
11
12
 
@@ -287,11 +288,20 @@ cancel_scope_names = (
287
288
  )
288
289
 
289
290
 
291
+ @dataclass
292
+ class MatchingCall:
293
+ node: ast.Call
294
+ name: str
295
+ base: str
296
+
297
+ def __str__(self) -> str:
298
+ return self.base + "." + self.name
299
+
300
+
290
301
  # convenience function used in a lot of visitors
291
- # should probably return a named tuple
292
302
  def get_matching_call(
293
303
  node: ast.AST, *names: str, base: Iterable[str] = ("trio", "anyio")
294
- ) -> tuple[ast.Call, str, str] | None:
304
+ ) -> MatchingCall | None:
295
305
  if isinstance(base, str):
296
306
  base = (base,)
297
307
  if (
@@ -301,7 +311,7 @@ def get_matching_call(
301
311
  and node.func.value.id in base
302
312
  and node.func.attr in names
303
313
  ):
304
- return node, node.func.attr, node.func.value.id
314
+ return MatchingCall(node, node.func.attr, node.func.value.id)
305
315
  return None
306
316
 
307
317
 
@@ -34,7 +34,7 @@ class Visitor102(Flake8AsyncVisitor):
34
34
  }
35
35
 
36
36
  class TrioScope:
37
- def __init__(self, node: ast.Call, funcname: str, _):
37
+ def __init__(self, node: ast.Call, funcname: str):
38
38
  super().__init__()
39
39
  self.node = node
40
40
  self.funcname = funcname
@@ -126,7 +126,7 @@ class Visitor102(Flake8AsyncVisitor):
126
126
  if call is None:
127
127
  continue
128
128
 
129
- trio_scope = self.TrioScope(*call)
129
+ trio_scope = self.TrioScope(call.node, call.name)
130
130
  # check if it's saved in a variable
131
131
  if isinstance(item.optional_vars, ast.Name):
132
132
  trio_scope.variable_name = item.optional_vars.id
@@ -199,6 +199,24 @@ class Visitor103_104(Flake8AsyncVisitor):
199
199
  # if body didn't raise, or it's unraised after else, set unraise
200
200
  self.unraised = not body_raised or self.unraised
201
201
 
202
+ def visit_Match(self, node: ast.Match): # type: ignore[name-defined]
203
+ if not self.unraised:
204
+ return
205
+ all_cases_raise = True
206
+ has_fallback = False
207
+ for case in node.cases:
208
+ # check for "bare pattern", i.e `case varname:`
209
+ has_fallback |= (
210
+ case.guard is None
211
+ and isinstance(case.pattern, ast.MatchAs) # type: ignore[attr-defined]
212
+ and case.pattern.pattern is None
213
+ )
214
+ self.visit_nodes(case.body)
215
+ all_cases_raise &= not self.unraised
216
+ self.unraised = True
217
+
218
+ self.unraised = not (all_cases_raise and has_fallback)
219
+
202
220
  # A loop is guaranteed to raise if:
203
221
  # condition always raises, or
204
222
  # else always raises, and
@@ -198,6 +198,22 @@ class TryState:
198
198
  )
199
199
 
200
200
 
201
+ @dataclass
202
+ class MatchState:
203
+ # TryState, LoopState, and MatchState all do fairly similar things. It would be nice
204
+ # to harmonize them and share logic.
205
+ base_uncheckpointed_statements: set[Statement] = field(default_factory=set)
206
+ case_uncheckpointed_statements: set[Statement] = field(default_factory=set)
207
+ has_fallback: bool = False
208
+
209
+ def copy(self):
210
+ return MatchState(
211
+ base_uncheckpointed_statements=self.base_uncheckpointed_statements.copy(),
212
+ case_uncheckpointed_statements=self.case_uncheckpointed_statements.copy(),
213
+ has_fallback=self.has_fallback,
214
+ )
215
+
216
+
201
217
  def checkpoint_statement(library: str) -> cst.SimpleStatementLine:
202
218
  # logic before this should stop code from wanting to insert the non-existing
203
219
  # asyncio.lowlevel.checkpoint
@@ -373,6 +389,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
373
389
 
374
390
  self.loop_state = LoopState()
375
391
  self.try_state = TryState()
392
+ self.match_state = MatchState()
376
393
 
377
394
  # ASYNC100
378
395
  self.has_checkpoint_stack: list[bool] = []
@@ -500,7 +517,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
500
517
 
501
518
  # visit body
502
519
  # we're not gonna get FlattenSentinel or RemovalSentinel
503
- self.new_body = cast(cst.BaseSuite, node.body.visit(self))
520
+ self.new_body = cast("cst.BaseSuite", node.body.visit(self))
504
521
 
505
522
  # we know that leave_FunctionDef for this FunctionDef will run immediately after
506
523
  # this function exits so we don't need to worry about save_state for new_body
@@ -894,6 +911,55 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
894
911
  self.leave_If(node, node) # type: ignore
895
912
  return False # libcst shouldn't visit subnodes again
896
913
 
914
+ def leave_Match_subject(self, node: cst.Match) -> None:
915
+ # We start the match logic after parsing the subject, instead of visit_Match,
916
+ # since the subject is always executed and might checkpoint.
917
+ if not self.async_function:
918
+ return
919
+ self.save_state(node, "match_state", copy=True)
920
+ self.match_state = MatchState(
921
+ base_uncheckpointed_statements=self.uncheckpointed_statements.copy()
922
+ )
923
+
924
+ def visit_MatchCase(self, node: cst.MatchCase) -> None:
925
+ # enter each case from the state after parsing the subject
926
+ self.uncheckpointed_statements = self.match_state.base_uncheckpointed_statements
927
+
928
+ def leave_MatchCase_guard(self, node: cst.MatchCase) -> None:
929
+ # `case _:` is no pattern and no guard, which means we know body is executed.
930
+ # But we also know that `case _ if <guard>:` is guaranteed to execute the guard,
931
+ # so for later logic we can treat them the same *if* there's no pattern and that
932
+ # guard checkpoints.
933
+ if (
934
+ isinstance(node.pattern, cst.MatchAs)
935
+ and node.pattern.pattern is None
936
+ and (node.guard is None or not self.uncheckpointed_statements)
937
+ ):
938
+ self.match_state.has_fallback = True
939
+
940
+ def leave_MatchCase(
941
+ self, original_node: cst.MatchCase, updated_node: cst.MatchCase
942
+ ) -> cst.MatchCase:
943
+ # collect the state at the end of each case
944
+ self.match_state.case_uncheckpointed_statements.update(
945
+ self.uncheckpointed_statements
946
+ )
947
+ return updated_node
948
+
949
+ def leave_Match(
950
+ self, original_node: cst.Match, updated_node: cst.Match
951
+ ) -> cst.Match:
952
+ # leave the Match with the worst-case of all branches
953
+ self.uncheckpointed_statements = self.match_state.case_uncheckpointed_statements
954
+ # if no fallback, also add the state at entering the match (after parsing subject)
955
+ if not self.match_state.has_fallback:
956
+ self.uncheckpointed_statements.update(
957
+ self.match_state.base_uncheckpointed_statements
958
+ )
959
+
960
+ self.restore_state(original_node)
961
+ return updated_node
962
+
897
963
  def visit_While(self, node: cst.While | cst.For):
898
964
  self.save_state(
899
965
  node,
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
17
17
  from re import Match
18
18
 
19
19
  import libcst as cst
20
- from libcst._position import CodeRange
20
+ from libcst.metadata import CodeRange
21
21
 
22
22
 
23
23
  @utility_visitor
@@ -287,8 +287,7 @@ class Visitor115(Flake8AsyncVisitor):
287
287
  and isinstance(node.args[0], ast.Constant)
288
288
  and node.args[0].value == 0
289
289
  ):
290
- # m[2] is set to node.func.value.id
291
- self.error(node, m[2])
290
+ self.error(node, m.base)
292
291
 
293
292
 
294
293
  @error_class
@@ -326,7 +325,7 @@ class Visitor116(Flake8AsyncVisitor):
326
325
  and arg.value > 86400
327
326
  )
328
327
  ):
329
- self.error(node, m[2])
328
+ self.error(node, m.base)
330
329
 
331
330
 
332
331
  @error_class
@@ -452,7 +451,49 @@ class Visitor122(Flake8AsyncVisitor):
452
451
  node, "fail_after", "move_on_after", base=("trio", "anyio")
453
452
  )
454
453
  ):
455
- self.error(node, f"{match[2]}.{match[1]}")
454
+ self.error(node, str(match))
455
+
456
+
457
+ @error_class
458
+ class Visitor125(Flake8AsyncVisitor):
459
+ error_codes: Mapping[str, str] = {
460
+ "ASYNC125": (
461
+ "Using {} with a constant value is nonsensical, as the value is relative "
462
+ "to the runner clock. Use ``fail_after(...)``, ``move_on_after(...)``, "
463
+ "``CancelScope(relative_deadline=...)`` or calculate it relative to "
464
+ "``{}.current_time()``."
465
+ )
466
+ }
467
+
468
+ def visit_Call(self, node: ast.Call):
469
+ def is_constant(value: ast.expr) -> bool:
470
+ if isinstance(value, ast.Constant):
471
+ return True
472
+ if isinstance(value, ast.BinOp):
473
+ return is_constant(value.left) and is_constant(value.right)
474
+ return False
475
+
476
+ match = get_matching_call(
477
+ node, "fail_at", "move_on_at", "CancelScope", base=("trio", "anyio")
478
+ )
479
+ if match is None:
480
+ return
481
+
482
+ if match.name in ("fail_at", "move_on_at") and len(node.args) == 1:
483
+ value = node.args[0]
484
+ else:
485
+ for kw in node.keywords:
486
+ if kw.arg == "deadline":
487
+ value = kw.value
488
+ break
489
+ else:
490
+ return
491
+ if is_constant(value):
492
+ self.error(
493
+ value,
494
+ str(match),
495
+ match.base,
496
+ )
456
497
 
457
498
 
458
499
  @error_class_cst
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: flake8-async
3
- Version: 25.3.1
3
+ Version: 25.4.2
4
4
  Summary: A highly opinionated flake8 plugin for Trio-related problems.
5
5
  Author: Zac Hatfield-Dodds, John Litborn, and Contributors
6
6
  Author-email: zac@zhd.dev
@@ -53,6 +53,7 @@ tests/autofix_files/async911_insert_library.py
53
53
  tests/autofix_files/async913.py
54
54
  tests/autofix_files/async913_trio_anyio.py
55
55
  tests/autofix_files/async91x_autofix.py
56
+ tests/autofix_files/async91x_py310.py
56
57
  tests/autofix_files/async91x_py311.py
57
58
  tests/autofix_files/exception_suppress_context_manager.py
58
59
  tests/autofix_files/exception_suppress_context_manager_import_star.py
@@ -73,6 +74,7 @@ tests/eval_files/async102_anyio.py
73
74
  tests/eval_files/async102_asyncio.py
74
75
  tests/eval_files/async102_trio.py
75
76
  tests/eval_files/async103.py
77
+ tests/eval_files/async103_104_py310.py
76
78
  tests/eval_files/async103_104_py311.py
77
79
  tests/eval_files/async103_all_imported.py
78
80
  tests/eval_files/async103_both_imported.py
@@ -107,6 +109,7 @@ tests/eval_files/async123.py
107
109
  tests/eval_files/async123_py311.py
108
110
  tests/eval_files/async124.py
109
111
  tests/eval_files/async124_no_autofix.py
112
+ tests/eval_files/async125.py
110
113
  tests/eval_files/async200.py
111
114
  tests/eval_files/async210.py
112
115
  tests/eval_files/async211.py
@@ -135,6 +138,7 @@ tests/eval_files/async913.py
135
138
  tests/eval_files/async913_trio_anyio.py
136
139
  tests/eval_files/async91x_autofix.py
137
140
  tests/eval_files/async91x_noautofix.py
141
+ tests/eval_files/async91x_py310.py
138
142
  tests/eval_files/async91x_py311.py
139
143
  tests/eval_files/exception_suppress_context_manager.py
140
144
  tests/eval_files/exception_suppress_context_manager_import_star.py
@@ -0,0 +1,90 @@
1
+ # ARG --enable=ASYNC910,ASYNC911,ASYNC913
2
+ # AUTOFIX
3
+ # ASYNCIO_NO_AUTOFIX
4
+ import trio
5
+
6
+
7
+ async def foo(): ...
8
+
9
+
10
+ async def match_subject() -> None:
11
+ match await foo():
12
+ case False:
13
+ pass
14
+
15
+
16
+ async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
17
+ None
18
+ ):
19
+ match foo():
20
+ case 1:
21
+ ...
22
+ case _:
23
+ await foo()
24
+ await trio.lowlevel.checkpoint()
25
+
26
+
27
+ async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
28
+ None
29
+ ):
30
+ match foo():
31
+ case 1:
32
+ await foo()
33
+ case 2:
34
+ await foo()
35
+ case _ if True:
36
+ await foo()
37
+ await trio.lowlevel.checkpoint()
38
+
39
+
40
+ async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
41
+ None
42
+ ):
43
+ match foo():
44
+ case 1:
45
+ await foo()
46
+ case 2:
47
+ await foo()
48
+ case _ if foo():
49
+ await foo()
50
+ await trio.lowlevel.checkpoint()
51
+
52
+
53
+ async def match_all_cases() -> None:
54
+ match foo():
55
+ case 1:
56
+ await foo()
57
+ case 2:
58
+ await foo()
59
+ case _:
60
+ await foo()
61
+
62
+
63
+ async def match_fallback_await_in_guard() -> None:
64
+ # The case guard is only executed if the pattern matches, so we can mostly treat
65
+ # it as part of the body, except for a special case for fallback+checkpointing guard.
66
+ match foo():
67
+ case 1 if await foo():
68
+ ...
69
+ case _ if await foo():
70
+ ...
71
+
72
+
73
+ async def match_checkpoint_guard() -> None:
74
+ # The above pattern is quite cursed, but this seems fairly reasonable to do.
75
+ match foo():
76
+ case 1 if await foo():
77
+ ...
78
+ case _:
79
+ await foo()
80
+
81
+
82
+ async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
83
+ None
84
+ ):
85
+ match foo():
86
+ case 1:
87
+ ...
88
+ case _ if await foo():
89
+ ...
90
+ await trio.lowlevel.checkpoint()
@@ -8,6 +8,8 @@ import sys
8
8
  from pathlib import Path
9
9
  from typing import TYPE_CHECKING, NamedTuple, TypeVar
10
10
 
11
+ from typing_extensions import Self
12
+
11
13
  if TYPE_CHECKING:
12
14
  from collections.abc import Iterable
13
15
 
@@ -27,7 +29,7 @@ class Version(NamedTuple):
27
29
  patch: int
28
30
 
29
31
  @classmethod
30
- def from_string(cls: type[T], string: str) -> T:
32
+ def from_string(cls, string: str) -> Self:
31
33
  return cls(*map(int, string.split(".")))
32
34
 
33
35
  def __str__(self) -> str:
@@ -0,0 +1,58 @@
1
+ """Test for ASYNC103/ASYNC104 with structural pattern matching
2
+
3
+ ASYNC103: no-reraise-cancelled
4
+ ASYNC104: cancelled-not-raised
5
+ """
6
+
7
+ # ARG --enable=ASYNC103,ASYNC104
8
+
9
+
10
+ def foo() -> Any: ...
11
+
12
+
13
+ try:
14
+ ...
15
+ except BaseException as e: # ASYNC103_trio: 7, "BaseException"
16
+ match foo():
17
+ case True:
18
+ raise e
19
+ case False:
20
+ ...
21
+ case _:
22
+ raise e
23
+
24
+ try:
25
+ ...
26
+ except BaseException: # ASYNC103_trio: 7, "BaseException"
27
+ match foo():
28
+ case True:
29
+ raise
30
+
31
+ try:
32
+ ...
33
+ except BaseException: # safe
34
+ match foo():
35
+ case True:
36
+ raise
37
+ case False:
38
+ raise
39
+ case _:
40
+ raise
41
+ try:
42
+ ...
43
+ except BaseException: # ASYNC103_trio: 7, "BaseException"
44
+ match foo():
45
+ case _ if foo():
46
+ raise
47
+ try:
48
+ ...
49
+ except BaseException: # ASYNC103_trio: 7, "BaseException"
50
+ match foo():
51
+ case 1:
52
+ return # ASYNC104: 12
53
+ case 2:
54
+ raise
55
+ case 3:
56
+ return # ASYNC104: 12
57
+ case blah:
58
+ raise
@@ -0,0 +1,33 @@
1
+ import trio
2
+ from typing import Final
3
+
4
+ # ASYNCIO_NO_ERROR
5
+ # anyio.[fail/move_on]_at doesn't exist, but no harm in erroring if we encounter them
6
+
7
+ trio.fail_at(5) # ASYNC125: 13, "trio.fail_at", "trio"
8
+ trio.fail_at(deadline=5) # ASYNC125: 22, "trio.fail_at", "trio"
9
+ trio.move_on_at(10**3) # ASYNC125: 16, "trio.move_on_at", "trio"
10
+ trio.fail_at(7 * 3 + 2 / 5 - (8**7)) # ASYNC125: 13, "trio.fail_at", "trio"
11
+
12
+ trio.CancelScope(deadline=7) # ASYNC125: 26, "trio.CancelScope", "trio"
13
+ trio.CancelScope(shield=True, deadline=7) # ASYNC125: 39, "trio.CancelScope", "trio"
14
+
15
+ # we *could* tell them to use math.inf here ...
16
+ trio.fail_at(10**1000) # ASYNC125: 13, "trio.fail_at", "trio"
17
+
18
+ # _after is fine
19
+ trio.fail_after(5)
20
+ trio.move_on_after(2.3)
21
+
22
+ trio.fail_at(trio.current_time())
23
+ trio.fail_at(trio.current_time() + 7)
24
+
25
+ # relative_deadline is fine, though anyio doesn't have it
26
+ trio.CancelScope(relative_deadline=7)
27
+
28
+ # does not trigger on other "constants".. but we could opt to trigger on
29
+ # any all-caps variable, or on :Final
30
+ MY_CONST_VALUE = 7
31
+ trio.fail_at(MY_CONST_VALUE)
32
+ my_final_value: Final = 3
33
+ trio.fail_at(my_final_value)
@@ -0,0 +1,86 @@
1
+ # ARG --enable=ASYNC910,ASYNC911,ASYNC913
2
+ # AUTOFIX
3
+ # ASYNCIO_NO_AUTOFIX
4
+ import trio
5
+
6
+
7
+ async def foo(): ...
8
+
9
+
10
+ async def match_subject() -> None:
11
+ match await foo():
12
+ case False:
13
+ pass
14
+
15
+
16
+ async def match_not_all_cases() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
17
+ None
18
+ ):
19
+ match foo():
20
+ case 1:
21
+ ...
22
+ case _:
23
+ await foo()
24
+
25
+
26
+ async def match_no_fallback() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
27
+ None
28
+ ):
29
+ match foo():
30
+ case 1:
31
+ await foo()
32
+ case 2:
33
+ await foo()
34
+ case _ if True:
35
+ await foo()
36
+
37
+
38
+ async def match_fallback_is_guarded() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
39
+ None
40
+ ):
41
+ match foo():
42
+ case 1:
43
+ await foo()
44
+ case 2:
45
+ await foo()
46
+ case _ if foo():
47
+ await foo()
48
+
49
+
50
+ async def match_all_cases() -> None:
51
+ match foo():
52
+ case 1:
53
+ await foo()
54
+ case 2:
55
+ await foo()
56
+ case _:
57
+ await foo()
58
+
59
+
60
+ async def match_fallback_await_in_guard() -> None:
61
+ # The case guard is only executed if the pattern matches, so we can mostly treat
62
+ # it as part of the body, except for a special case for fallback+checkpointing guard.
63
+ match foo():
64
+ case 1 if await foo():
65
+ ...
66
+ case _ if await foo():
67
+ ...
68
+
69
+
70
+ async def match_checkpoint_guard() -> None:
71
+ # The above pattern is quite cursed, but this seems fairly reasonable to do.
72
+ match foo():
73
+ case 1 if await foo():
74
+ ...
75
+ case _:
76
+ await foo()
77
+
78
+
79
+ async def match_not_checkpoint_in_all_guards() -> ( # ASYNC910: 0, "exit", Statement("function definition", lineno)
80
+ None
81
+ ):
82
+ match foo():
83
+ case 1:
84
+ ...
85
+ case _ if await foo():
86
+ ...
@@ -510,6 +510,7 @@ error_codes_ignored_when_checking_transformed_sync_code = {
510
510
  "ASYNC121",
511
511
  "ASYNC122",
512
512
  "ASYNC123",
513
+ "ASYNC125",
513
514
  "ASYNC300",
514
515
  "ASYNC912",
515
516
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes