flake8-async 24.3.4__tar.gz → 24.3.6__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 (112) hide show
  1. {flake8-async-24.3.4 → flake8-async-24.3.6}/CHANGELOG.md +7 -1
  2. {flake8-async-24.3.4 → flake8-async-24.3.6}/PKG-INFO +10 -4
  3. {flake8-async-24.3.4 → flake8-async-24.3.6}/README.md +2 -2
  4. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/__init__.py +1 -1
  5. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor100.py +3 -1
  6. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor102.py +29 -5
  7. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async.egg-info/PKG-INFO +10 -4
  8. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async.egg-info/SOURCES.txt +2 -0
  9. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/autofix_files/async100.py +6 -0
  10. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/autofix_files/noqa.py +19 -9
  11. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async100.py +6 -0
  12. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async102.py +13 -0
  13. flake8-async-24.3.6/tests/eval_files/async102_aclose.py +22 -0
  14. flake8-async-24.3.6/tests/eval_files/async102_aclose_args.py +24 -0
  15. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/noqa.py +17 -7
  16. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_messages_documented.py +3 -1
  17. {flake8-async-24.3.4 → flake8-async-24.3.6}/CONTRIBUTING.md +0 -0
  18. {flake8-async-24.3.4 → flake8-async-24.3.6}/LICENSE +0 -0
  19. {flake8-async-24.3.4 → flake8-async-24.3.6}/MANIFEST.in +0 -0
  20. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/__main__.py +0 -0
  21. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/base.py +0 -0
  22. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/runner.py +0 -0
  23. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/__init__.py +0 -0
  24. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/flake8asyncvisitor.py +0 -0
  25. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/helpers.py +0 -0
  26. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor101.py +0 -0
  27. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor103_104.py +0 -0
  28. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor105.py +0 -0
  29. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor111.py +0 -0
  30. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor118.py +0 -0
  31. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor2xx.py +0 -0
  32. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor91x.py +0 -0
  33. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitor_utility.py +0 -0
  34. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async/visitors/visitors.py +0 -0
  35. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async.egg-info/dependency_links.txt +0 -0
  36. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async.egg-info/entry_points.txt +0 -0
  37. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async.egg-info/not-zip-safe +0 -0
  38. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async.egg-info/requires.txt +0 -0
  39. {flake8-async-24.3.4 → flake8-async-24.3.6}/flake8_async.egg-info/top_level.txt +0 -0
  40. {flake8-async-24.3.4 → flake8-async-24.3.6}/pyproject.toml +0 -0
  41. {flake8-async-24.3.4 → flake8-async-24.3.6}/setup.cfg +0 -0
  42. {flake8-async-24.3.4 → flake8-async-24.3.6}/setup.py +0 -0
  43. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/__init__.py +0 -0
  44. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/autofix_files/async100_simple_autofix.py +0 -0
  45. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/autofix_files/async910.py +0 -0
  46. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/autofix_files/async911.py +0 -0
  47. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/autofix_files/async91x_autofix.py +0 -0
  48. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/autofix_files/noqa_testing.py +0 -0
  49. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/conftest.py +0 -0
  50. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/anyio_trio.py +0 -0
  51. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async100_asyncio.py +0 -0
  52. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async100_noautofix.py +0 -0
  53. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async100_simple_autofix.py +0 -0
  54. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async101.py +0 -0
  55. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async102_anyio.py +0 -0
  56. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async102_asyncio.py +0 -0
  57. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async102_trio.py +0 -0
  58. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async103.py +0 -0
  59. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async103_all_imported.py +0 -0
  60. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async103_both_imported.py +0 -0
  61. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async103_no_104.py +0 -0
  62. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async103_trio.py +0 -0
  63. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async104.py +0 -0
  64. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async104_anyio.py +0 -0
  65. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async104_trio.py +0 -0
  66. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async105.py +0 -0
  67. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async105_anyio.py +0 -0
  68. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async106.py +0 -0
  69. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async109.py +0 -0
  70. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async110.py +0 -0
  71. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async111.py +0 -0
  72. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async112.py +0 -0
  73. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async113.py +0 -0
  74. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async113_anyio.py +0 -0
  75. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async113_trio.py +0 -0
  76. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async114.py +0 -0
  77. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async115.py +0 -0
  78. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async116.py +0 -0
  79. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async118.py +0 -0
  80. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async200.py +0 -0
  81. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async210.py +0 -0
  82. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async211.py +0 -0
  83. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async212.py +0 -0
  84. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async22x.py +0 -0
  85. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async22x_asyncio.py +0 -0
  86. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async232.py +0 -0
  87. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async232_asyncio.py +0 -0
  88. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async23x.py +0 -0
  89. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async23x_asyncio.py +0 -0
  90. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async240.py +0 -0
  91. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async250.py +0 -0
  92. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async250_multi_library.py +0 -0
  93. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async251.py +0 -0
  94. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async251_multi_library.py +0 -0
  95. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async900.py +0 -0
  96. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async910.py +0 -0
  97. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async911.py +0 -0
  98. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async91x_autofix.py +0 -0
  99. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/async91x_noautofix.py +0 -0
  100. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/no_library.py +0 -0
  101. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/noqa_no_autofix.py +0 -0
  102. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/noqa_testing.py +0 -0
  103. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/eval_files/trio_anyio.py +0 -0
  104. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_all_visitors_imported.py +0 -0
  105. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_changelog_and_version.py +0 -0
  106. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_config_and_args.py +0 -0
  107. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_decorator.py +0 -0
  108. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_exception_on_invalid_code.py +0 -0
  109. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_flake8_async.py +0 -0
  110. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/test_formatting.py +0 -0
  111. {flake8-async-24.3.4 → flake8-async-24.3.6}/tests/trio_options.py +0 -0
  112. {flake8-async-24.3.4 → flake8-async-24.3.6}/tox.ini +0 -0
@@ -1,8 +1,14 @@
1
1
  # Changelog
2
2
  *[CalVer, YY.month.patch](https://calver.org/)*
3
3
 
4
+ ## 24.3.6
5
+ - ASYNC100 no longer triggers if a context manager contains a `yield`.
6
+
7
+ ## 24.3.5
8
+ - ASYNC102 (no await inside finally or critical except) no longer raises warnings for calls to `aclose()` on objects in trio/anyio code. See https://github.com/python-trio/flake8-async/issues/156
9
+
4
10
  ## 24.3.4
5
- - ASYNC110 (don't loop sleep) now also warns if looping `[trio/anyio].lowlevel.checkpoint()`
11
+ - ASYNC110 (don't loop sleep) now also warns if looping `[trio/anyio].lowlevel.checkpoint()`.
6
12
 
7
13
  ## 24.3.3
8
14
  - Add ASYNC251: `time.sleep()` in async method.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flake8-async
3
- Version: 24.3.4
3
+ Version: 24.3.6
4
4
  Summary: A highly opinionated flake8 plugin for Trio-related problems.
5
5
  Home-page: https://github.com/python-trio/flake8-async
6
6
  Author: Zac Hatfield-Dodds, John Litborn, and Contributors
@@ -50,7 +50,7 @@ pip install flake8-async
50
50
  ## List of warnings
51
51
  - **ASYNC100**: A `with [trio/anyio].fail_after(...):` or `with [trio/anyio].move_on_after(...):`
52
52
  context does not contain any `await` statements. This makes it pointless, as
53
- the timeout can only be triggered by a checkpoint.
53
+ the timeout can only be triggered by a checkpoint. This check also allows `yield` statements, since checkpoints can happen in the caller we yield to.
54
54
  - **ASYNC101**: `yield` inside a trio/anyio nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
55
55
  - **ASYNC102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError` unless you use a shielded cancel scope with a timeout. This is currently not able to detect asyncio shields.
56
56
  - **ASYNC103**: `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError`, or a bare `except:` with a code path that doesn't re-raise. If you don't want to re-raise `BaseException`, add a separate handler for `trio.Cancelled`/`anyio.get_cancelled_exc_class()`/`asyncio.exceptions.CancelledError` before.
@@ -109,7 +109,7 @@ adding the following to your `.pre-commit-config.yaml`:
109
109
  minimum_pre_commit_version: '2.9.0'
110
110
  repos:
111
111
  - repo: https://github.com/python-trio/flake8-async
112
- rev: 24.3.4
112
+ rev: 24.3.6
113
113
  hooks:
114
114
  - id: flake8-async
115
115
  # args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
@@ -221,8 +221,14 @@ async def my_function():
221
221
  # Changelog
222
222
  *[CalVer, YY.month.patch](https://calver.org/)*
223
223
 
224
+ ## 24.3.6
225
+ - ASYNC100 no longer triggers if a context manager contains a `yield`.
226
+
227
+ ## 24.3.5
228
+ - ASYNC102 (no await inside finally or critical except) no longer raises warnings for calls to `aclose()` on objects in trio/anyio code. See https://github.com/python-trio/flake8-async/issues/156
229
+
224
230
  ## 24.3.4
225
- - ASYNC110 (don't loop sleep) now also warns if looping `[trio/anyio].lowlevel.checkpoint()`
231
+ - ASYNC110 (don't loop sleep) now also warns if looping `[trio/anyio].lowlevel.checkpoint()`.
226
232
 
227
233
  ## 24.3.3
228
234
  - Add ASYNC251: `time.sleep()` in async method.
@@ -25,7 +25,7 @@ pip install flake8-async
25
25
  ## List of warnings
26
26
  - **ASYNC100**: A `with [trio/anyio].fail_after(...):` or `with [trio/anyio].move_on_after(...):`
27
27
  context does not contain any `await` statements. This makes it pointless, as
28
- the timeout can only be triggered by a checkpoint.
28
+ the timeout can only be triggered by a checkpoint. This check also allows `yield` statements, since checkpoints can happen in the caller we yield to.
29
29
  - **ASYNC101**: `yield` inside a trio/anyio nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
30
30
  - **ASYNC102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError` unless you use a shielded cancel scope with a timeout. This is currently not able to detect asyncio shields.
31
31
  - **ASYNC103**: `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError`, or a bare `except:` with a code path that doesn't re-raise. If you don't want to re-raise `BaseException`, add a separate handler for `trio.Cancelled`/`anyio.get_cancelled_exc_class()`/`asyncio.exceptions.CancelledError` before.
@@ -84,7 +84,7 @@ adding the following to your `.pre-commit-config.yaml`:
84
84
  minimum_pre_commit_version: '2.9.0'
85
85
  repos:
86
86
  - repo: https://github.com/python-trio/flake8-async
87
- rev: 24.3.4
87
+ rev: 24.3.6
88
88
  hooks:
89
89
  - id: flake8-async
90
90
  # args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
@@ -37,7 +37,7 @@ if TYPE_CHECKING:
37
37
 
38
38
 
39
39
  # CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
40
- __version__ = "24.3.4"
40
+ __version__ = "24.3.6"
41
41
 
42
42
 
43
43
  # taken from https://github.com/Zac-HD/shed
@@ -74,9 +74,11 @@ class Visitor100_libcst(Flake8AsyncVisitor_cst):
74
74
  if node.asynchronous is not None:
75
75
  self.checkpoint()
76
76
 
77
- def visit_Await(self, node: cst.Await | cst.For | cst.With):
77
+ def visit_Await(self, node: cst.Await | cst.Yield):
78
78
  self.checkpoint()
79
79
 
80
+ visit_Yield = visit_Await
81
+
80
82
  def visit_FunctionDef(self, node: cst.FunctionDef):
81
83
  self.save_state(node, "has_checkpoint_stack", copy=True)
82
84
  self.has_checkpoint_stack = []
@@ -52,15 +52,35 @@ class Visitor102(Flake8AsyncVisitor):
52
52
  self._trio_context_managers: list[Visitor102.TrioScope] = []
53
53
  self.cancelled_caught = False
54
54
 
55
- # if we're inside a finally, and we're not inside a scope that doesn't have
56
- # both a timeout and shield
57
- def visit_Await(self, node: ast.Await | ast.AsyncFor | ast.AsyncWith):
55
+ # if we're inside a finally or critical except, and we're not inside a scope that
56
+ # doesn't have both a timeout and shield
57
+ def async_call_checker(
58
+ self, node: ast.Await | ast.AsyncFor | ast.AsyncWith
59
+ ) -> None:
58
60
  if self._critical_scope is not None and not any(
59
61
  cm.has_timeout and cm.shielded for cm in self._trio_context_managers
60
62
  ):
61
63
  self.error(node, self._critical_scope)
62
64
 
63
- visit_AsyncFor = visit_Await
65
+ def is_safe_aclose_call(self, node: ast.Await) -> bool:
66
+ return (
67
+ # don't mark calls safe in asyncio-only files
68
+ # a more defensive option would be `asyncio not in self.library`
69
+ self.library != ("asyncio",)
70
+ and isinstance(node.value, ast.Call)
71
+ # only known safe if no arguments
72
+ and not node.value.args
73
+ and not node.value.keywords
74
+ and isinstance(node.value.func, ast.Attribute)
75
+ and node.value.func.attr == "aclose"
76
+ )
77
+
78
+ def visit_Await(self, node: ast.Await):
79
+ # allow calls to `.aclose()`
80
+ if not (self.is_safe_aclose_call(node)):
81
+ self.async_call_checker(node)
82
+
83
+ visit_AsyncFor = async_call_checker
64
84
 
65
85
  def visit_With(self, node: ast.With | ast.AsyncWith):
66
86
  self.save_state(node, "_trio_context_managers", copy=True)
@@ -82,7 +102,7 @@ class Visitor102(Flake8AsyncVisitor):
82
102
  break
83
103
 
84
104
  def visit_AsyncWith(self, node: ast.AsyncWith):
85
- self.visit_Await(node)
105
+ self.async_call_checker(node)
86
106
  self.visit_With(node)
87
107
 
88
108
  def visit_Try(self, node: ast.Try):
@@ -94,6 +114,10 @@ class Visitor102(Flake8AsyncVisitor):
94
114
  self.visit_nodes(node.body, node.handlers, node.orelse)
95
115
 
96
116
  self._trio_context_managers = []
117
+ # node.finalbody does not have a lineno, so we give the position of the try
118
+ # it'd be possible to estimate the lineno given the last except and the first
119
+ # statement in the finally, but it would be very hard to get it perfect with
120
+ # comments and empty lines and stuff.
97
121
  self._critical_scope = Statement("try/finally", node.lineno, node.col_offset)
98
122
  self.visit_nodes(node.finalbody)
99
123
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flake8-async
3
- Version: 24.3.4
3
+ Version: 24.3.6
4
4
  Summary: A highly opinionated flake8 plugin for Trio-related problems.
5
5
  Home-page: https://github.com/python-trio/flake8-async
6
6
  Author: Zac Hatfield-Dodds, John Litborn, and Contributors
@@ -50,7 +50,7 @@ pip install flake8-async
50
50
  ## List of warnings
51
51
  - **ASYNC100**: A `with [trio/anyio].fail_after(...):` or `with [trio/anyio].move_on_after(...):`
52
52
  context does not contain any `await` statements. This makes it pointless, as
53
- the timeout can only be triggered by a checkpoint.
53
+ the timeout can only be triggered by a checkpoint. This check also allows `yield` statements, since checkpoints can happen in the caller we yield to.
54
54
  - **ASYNC101**: `yield` inside a trio/anyio nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
55
55
  - **ASYNC102**: It's unsafe to await inside `finally:` or `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError` unless you use a shielded cancel scope with a timeout. This is currently not able to detect asyncio shields.
56
56
  - **ASYNC103**: `except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError`, or a bare `except:` with a code path that doesn't re-raise. If you don't want to re-raise `BaseException`, add a separate handler for `trio.Cancelled`/`anyio.get_cancelled_exc_class()`/`asyncio.exceptions.CancelledError` before.
@@ -109,7 +109,7 @@ adding the following to your `.pre-commit-config.yaml`:
109
109
  minimum_pre_commit_version: '2.9.0'
110
110
  repos:
111
111
  - repo: https://github.com/python-trio/flake8-async
112
- rev: 24.3.4
112
+ rev: 24.3.6
113
113
  hooks:
114
114
  - id: flake8-async
115
115
  # args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
@@ -221,8 +221,14 @@ async def my_function():
221
221
  # Changelog
222
222
  *[CalVer, YY.month.patch](https://calver.org/)*
223
223
 
224
+ ## 24.3.6
225
+ - ASYNC100 no longer triggers if a context manager contains a `yield`.
226
+
227
+ ## 24.3.5
228
+ - ASYNC102 (no await inside finally or critical except) no longer raises warnings for calls to `aclose()` on objects in trio/anyio code. See https://github.com/python-trio/flake8-async/issues/156
229
+
224
230
  ## 24.3.4
225
- - ASYNC110 (don't loop sleep) now also warns if looping `[trio/anyio].lowlevel.checkpoint()`
231
+ - ASYNC110 (don't loop sleep) now also warns if looping `[trio/anyio].lowlevel.checkpoint()`.
226
232
 
227
233
  ## 24.3.3
228
234
  - Add ASYNC251: `time.sleep()` in async method.
@@ -56,6 +56,8 @@ tests/eval_files/async100_noautofix.py
56
56
  tests/eval_files/async100_simple_autofix.py
57
57
  tests/eval_files/async101.py
58
58
  tests/eval_files/async102.py
59
+ tests/eval_files/async102_aclose.py
60
+ tests/eval_files/async102_aclose_args.py
59
61
  tests/eval_files/async102_anyio.py
60
62
  tests/eval_files/async102_asyncio.py
61
63
  tests/eval_files/async102_trio.py
@@ -114,3 +114,9 @@ async def more_nested_tests():
114
114
  with contextlib.suppress(Exception):
115
115
  with open("blah") as file:
116
116
  print("foo")
117
+
118
+
119
+ # Don't trigger for blocks with a yield statement
120
+ async def foo():
121
+ with trio.fail_after(1):
122
+ yield
@@ -1,47 +1,57 @@
1
1
  # AUTOFIX
2
2
  # NOASYNCIO - does not trigger on ASYNC100
3
- # ARG --enable=ASYNC100,ASYNC911
3
+ # ARG --enable=ASYNC100,ASYNC911,ASYNC910
4
4
  from typing import Any
5
5
 
6
6
  import trio
7
7
 
8
8
 
9
+ def foo() -> bool:
10
+ return False
11
+
12
+
9
13
  # fmt: off
10
14
  async def foo_no_noqa():
11
- await trio.lowlevel.checkpoint()
12
15
  # ASYNC100: 9, 'trio', 'fail_after'
13
- yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
16
+ if foo():
17
+ await trio.lowlevel.checkpoint()
18
+ return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
14
19
  await trio.lowlevel.checkpoint()
15
20
 
16
21
 
17
22
  async def foo_noqa_bare():
18
23
  with trio.fail_after(5): # noqa
19
- yield # noqa
24
+ if foo():
25
+ return # noqa
20
26
  await trio.lowlevel.checkpoint()
21
27
 
22
28
 
23
29
  async def foo_noqa_100():
24
30
  with trio.fail_after(5): # noqa: ASYNC100
25
- await trio.lowlevel.checkpoint()
26
- yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
31
+ if foo():
32
+ await trio.lowlevel.checkpoint()
33
+ return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
27
34
  await trio.lowlevel.checkpoint()
28
35
 
29
36
 
30
37
  async def foo_noqa_911():
31
38
  # ASYNC100: 9, 'trio', 'fail_after'
32
- yield # noqa: ASYNC911
39
+ if foo():
40
+ return # noqa: ASYNC910
33
41
  await trio.lowlevel.checkpoint()
34
42
 
35
43
 
36
44
  async def foo_noqa_100_911():
37
45
  with trio.fail_after(5): # noqa: ASYNC100, ASYNC911
38
- yield # noqa: ASYNC911
46
+ if foo():
47
+ return # noqa: ASYNC910
39
48
  await trio.lowlevel.checkpoint()
40
49
 
41
50
 
42
51
  async def foo_noqa_100_911_500():
43
52
  with trio.fail_after(5): # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
44
- yield # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
53
+ if foo():
54
+ return # noqa: ASYNC100, ASYNC910 , ASYNC500,,,
45
55
  await trio.lowlevel.checkpoint()
46
56
  # fmt: on
47
57
 
@@ -114,3 +114,9 @@ async def more_nested_tests():
114
114
  with contextlib.suppress(Exception):
115
115
  with open("blah") as file:
116
116
  print("foo")
117
+
118
+
119
+ # Don't trigger for blocks with a yield statement
120
+ async def foo():
121
+ with trio.fail_after(1):
122
+ yield
@@ -252,3 +252,16 @@ async def foo_nested_excepts():
252
252
  await foo()
253
253
  await foo()
254
254
  await foo()
255
+
256
+
257
+ # double check that this works nested inside an async for
258
+ async def foo_nested_async_for():
259
+
260
+ async for i in trio.bypasslinters:
261
+ try:
262
+ ...
263
+ except BaseException:
264
+ async for ( # error: 12, Statement("BaseException", lineno-1)
265
+ j
266
+ ) in trio.bypasslinters:
267
+ ...
@@ -0,0 +1,22 @@
1
+ # type: ignore
2
+
3
+ # exclude finally: await x.aclose() from async102, with trio/anyio
4
+ # ANYIO_NO_ERROR
5
+ # TRIO_NO_ERROR
6
+ # See also async102_aclose_args.py - which makes sure trio/anyio raises errors if there
7
+ # are arguments to aclose().
8
+
9
+
10
+ async def foo():
11
+ # no type tracking in this check, we allow any call that looks like
12
+ # `await [...].aclose()`
13
+ x = None
14
+
15
+ try:
16
+ ...
17
+ except BaseException:
18
+ await x.aclose() # ASYNC102: 8, Statement("BaseException", lineno-1)
19
+ await x.y.aclose() # ASYNC102: 8, Statement("BaseException", lineno-2)
20
+ finally:
21
+ await x.aclose() # ASYNC102: 8, Statement("try/finally", lineno-6)
22
+ await x.y.aclose() # ASYNC102: 8, Statement("try/finally", lineno-7)
@@ -0,0 +1,24 @@
1
+ # type: ignore
2
+
3
+ # trio/anyio should still raise errors if there's args
4
+ # asyncio will always raise errors
5
+
6
+ # See also async102_aclose.py, which checks that trio/anyio marks arg-less aclose() as safe
7
+
8
+
9
+ async def foo():
10
+ # no type tracking in this check
11
+ x = None
12
+
13
+ try:
14
+ ...
15
+ except BaseException:
16
+ await x.aclose(foo) # ASYNC102: 8, Statement("BaseException", lineno-1)
17
+ await x.aclose(bar=foo) # ASYNC102: 8, Statement("BaseException", lineno-2)
18
+ await x.aclose(*foo) # ASYNC102: 8, Statement("BaseException", lineno-3)
19
+ await x.aclose(None) # ASYNC102: 8, Statement("BaseException", lineno-4)
20
+ finally:
21
+ await x.aclose(foo) # ASYNC102: 8, Statement("try/finally", lineno-8)
22
+ await x.aclose(bar=foo) # ASYNC102: 8, Statement("try/finally", lineno-9)
23
+ await x.aclose(*foo) # ASYNC102: 8, Statement("try/finally", lineno-10)
24
+ await x.aclose(None) # ASYNC102: 8, Statement("try/finally", lineno-11)
@@ -1,45 +1,55 @@
1
1
  # AUTOFIX
2
2
  # NOASYNCIO - does not trigger on ASYNC100
3
- # ARG --enable=ASYNC100,ASYNC911
3
+ # ARG --enable=ASYNC100,ASYNC911,ASYNC910
4
4
  from typing import Any
5
5
 
6
6
  import trio
7
7
 
8
8
 
9
+ def foo() -> bool:
10
+ return False
11
+
12
+
9
13
  # fmt: off
10
14
  async def foo_no_noqa():
11
15
  with trio.fail_after(5): # ASYNC100: 9, 'trio', 'fail_after'
12
- yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
16
+ if foo():
17
+ return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
13
18
  await trio.lowlevel.checkpoint()
14
19
 
15
20
 
16
21
  async def foo_noqa_bare():
17
22
  with trio.fail_after(5): # noqa
18
- yield # noqa
23
+ if foo():
24
+ return # noqa
19
25
  await trio.lowlevel.checkpoint()
20
26
 
21
27
 
22
28
  async def foo_noqa_100():
23
29
  with trio.fail_after(5): # noqa: ASYNC100
24
- yield # ASYNC911: 8, "yield", Statement("function definition", lineno-2)
30
+ if foo():
31
+ return # ASYNC910: 12, "return", Statement("function definition", lineno-3)
25
32
  await trio.lowlevel.checkpoint()
26
33
 
27
34
 
28
35
  async def foo_noqa_911():
29
36
  with trio.fail_after(5): # ASYNC100: 9, 'trio', 'fail_after'
30
- yield # noqa: ASYNC911
37
+ if foo():
38
+ return # noqa: ASYNC910
31
39
  await trio.lowlevel.checkpoint()
32
40
 
33
41
 
34
42
  async def foo_noqa_100_911():
35
43
  with trio.fail_after(5): # noqa: ASYNC100, ASYNC911
36
- yield # noqa: ASYNC911
44
+ if foo():
45
+ return # noqa: ASYNC910
37
46
  await trio.lowlevel.checkpoint()
38
47
 
39
48
 
40
49
  async def foo_noqa_100_911_500():
41
50
  with trio.fail_after(5): # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
42
- yield # noqa: ASYNC100, ASYNC911 , ASYNC500,,,
51
+ if foo():
52
+ return # noqa: ASYNC100, ASYNC910 , ASYNC500,,,
43
53
  await trio.lowlevel.checkpoint()
44
54
  # fmt: on
45
55
 
@@ -44,7 +44,9 @@ def test_messages_documented():
44
44
  if not file_path.is_file():
45
45
  continue
46
46
 
47
- if m := re.search(r"async\d\d\d", str(file_path)):
47
+ # only look in the stem (final part of the path), so as not to get tripped
48
+ # up by [git worktree] directories with an exception code in the name
49
+ if m := re.search(r"^async\d\d\d", str(file_path.stem)):
48
50
  documented_errors["eval_files"].add(m.group().upper())
49
51
 
50
52
  with open(file_path) as file:
File without changes
File without changes
File without changes
File without changes
File without changes