flake8-async 24.3.6__tar.gz → 24.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 (113) hide show
  1. {flake8-async-24.3.6 → flake8_async-24.4.2}/CHANGELOG.md +6 -0
  2. {flake8-async-24.3.6 → flake8_async-24.4.2}/PKG-INFO +12 -2
  3. {flake8-async-24.3.6 → flake8_async-24.4.2}/README.md +5 -1
  4. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/__init__.py +1 -1
  5. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor91x.py +3 -1
  6. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitors.py +39 -0
  7. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async.egg-info/PKG-INFO +12 -2
  8. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async.egg-info/SOURCES.txt +1 -0
  9. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/autofix_files/async910.py +13 -0
  10. flake8_async-24.4.2/tests/eval_files/async119.py +64 -0
  11. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async910.py +12 -0
  12. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_config_and_args.py +31 -0
  13. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_flake8_async.py +1 -0
  14. {flake8-async-24.3.6 → flake8_async-24.4.2}/tox.ini +12 -0
  15. {flake8-async-24.3.6 → flake8_async-24.4.2}/CONTRIBUTING.md +0 -0
  16. {flake8-async-24.3.6 → flake8_async-24.4.2}/LICENSE +0 -0
  17. {flake8-async-24.3.6 → flake8_async-24.4.2}/MANIFEST.in +0 -0
  18. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/__main__.py +0 -0
  19. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/base.py +0 -0
  20. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/runner.py +0 -0
  21. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/__init__.py +0 -0
  22. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/flake8asyncvisitor.py +0 -0
  23. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/helpers.py +0 -0
  24. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor100.py +0 -0
  25. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor101.py +0 -0
  26. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor102.py +0 -0
  27. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor103_104.py +0 -0
  28. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor105.py +0 -0
  29. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor111.py +0 -0
  30. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor118.py +0 -0
  31. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor2xx.py +0 -0
  32. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async/visitors/visitor_utility.py +0 -0
  33. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async.egg-info/dependency_links.txt +0 -0
  34. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async.egg-info/entry_points.txt +0 -0
  35. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async.egg-info/not-zip-safe +0 -0
  36. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async.egg-info/requires.txt +0 -0
  37. {flake8-async-24.3.6 → flake8_async-24.4.2}/flake8_async.egg-info/top_level.txt +0 -0
  38. {flake8-async-24.3.6 → flake8_async-24.4.2}/pyproject.toml +0 -0
  39. {flake8-async-24.3.6 → flake8_async-24.4.2}/setup.cfg +0 -0
  40. {flake8-async-24.3.6 → flake8_async-24.4.2}/setup.py +0 -0
  41. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/__init__.py +0 -0
  42. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/autofix_files/async100.py +0 -0
  43. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/autofix_files/async100_simple_autofix.py +0 -0
  44. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/autofix_files/async911.py +0 -0
  45. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/autofix_files/async91x_autofix.py +0 -0
  46. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/autofix_files/noqa.py +0 -0
  47. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/autofix_files/noqa_testing.py +0 -0
  48. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/conftest.py +0 -0
  49. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/anyio_trio.py +0 -0
  50. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async100.py +0 -0
  51. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async100_asyncio.py +0 -0
  52. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async100_noautofix.py +0 -0
  53. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async100_simple_autofix.py +0 -0
  54. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async101.py +0 -0
  55. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async102.py +0 -0
  56. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async102_aclose.py +0 -0
  57. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async102_aclose_args.py +0 -0
  58. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async102_anyio.py +0 -0
  59. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async102_asyncio.py +0 -0
  60. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async102_trio.py +0 -0
  61. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async103.py +0 -0
  62. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async103_all_imported.py +0 -0
  63. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async103_both_imported.py +0 -0
  64. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async103_no_104.py +0 -0
  65. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async103_trio.py +0 -0
  66. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async104.py +0 -0
  67. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async104_anyio.py +0 -0
  68. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async104_trio.py +0 -0
  69. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async105.py +0 -0
  70. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async105_anyio.py +0 -0
  71. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async106.py +0 -0
  72. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async109.py +0 -0
  73. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async110.py +0 -0
  74. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async111.py +0 -0
  75. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async112.py +0 -0
  76. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async113.py +0 -0
  77. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async113_anyio.py +0 -0
  78. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async113_trio.py +0 -0
  79. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async114.py +0 -0
  80. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async115.py +0 -0
  81. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async116.py +0 -0
  82. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async118.py +0 -0
  83. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async200.py +0 -0
  84. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async210.py +0 -0
  85. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async211.py +0 -0
  86. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async212.py +0 -0
  87. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async22x.py +0 -0
  88. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async22x_asyncio.py +0 -0
  89. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async232.py +0 -0
  90. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async232_asyncio.py +0 -0
  91. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async23x.py +0 -0
  92. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async23x_asyncio.py +0 -0
  93. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async240.py +0 -0
  94. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async250.py +0 -0
  95. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async250_multi_library.py +0 -0
  96. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async251.py +0 -0
  97. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async251_multi_library.py +0 -0
  98. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async900.py +0 -0
  99. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async911.py +0 -0
  100. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async91x_autofix.py +0 -0
  101. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/async91x_noautofix.py +0 -0
  102. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/no_library.py +0 -0
  103. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/noqa.py +0 -0
  104. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/noqa_no_autofix.py +0 -0
  105. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/noqa_testing.py +0 -0
  106. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/eval_files/trio_anyio.py +0 -0
  107. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_all_visitors_imported.py +0 -0
  108. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_changelog_and_version.py +0 -0
  109. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_decorator.py +0 -0
  110. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_exception_on_invalid_code.py +0 -0
  111. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_formatting.py +0 -0
  112. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/test_messages_documented.py +0 -0
  113. {flake8-async-24.3.6 → flake8_async-24.4.2}/tests/trio_options.py +0 -0
@@ -1,6 +1,12 @@
1
1
  # Changelog
2
2
  *[CalVer, YY.month.patch](https://calver.org/)*
3
3
 
4
+ ## 24.4.2
5
+ - Add ASYNC119: yield in contextmanager in async generator.
6
+
7
+ ## 24.4.1
8
+ - ASYNC91X fix internal error caused by multiple `try/except` incorrectly sharing state.
9
+
4
10
  ## 24.3.6
5
11
  - ASYNC100 no longer triggers if a context manager contains a `yield`.
6
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flake8-async
3
- Version: 24.3.6
3
+ Version: 24.4.2
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
@@ -66,6 +66,7 @@ pip install flake8-async
66
66
  - **ASYNC115**: Replace `[trio/anyio].sleep(0)` with the more suggestive `[trio/anyio].lowlevel.checkpoint()`.
67
67
  - **ASYNC116**: `[trio/anyio].sleep()` with >24 hour interval should usually be `[trio/anyio].sleep_forever()`.
68
68
  - **ASYNC118**: Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable, since that breaks linter checks and multi-backend programs.
69
+ - **ASYNC119**: `yield` in context manager in async generator is unsafe, the cleanup may be delayed until `await` is no longer allowed. We strongly encourage you to read PEP-533 and use `async with aclosing(...)`, or better yet avoid async generators entirely (see ASYNC900) in favor of context managers which return an iterable channel/queue.
69
70
 
70
71
  ### Warnings for blocking sync calls in async functions
71
72
  Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
@@ -109,7 +110,7 @@ adding the following to your `.pre-commit-config.yaml`:
109
110
  minimum_pre_commit_version: '2.9.0'
110
111
  repos:
111
112
  - repo: https://github.com/python-trio/flake8-async
112
- rev: 24.3.6
113
+ rev: 24.4.2
113
114
  hooks:
114
115
  - id: flake8-async
115
116
  # args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
@@ -148,6 +149,9 @@ flake8-async **/*.py
148
149
  but we prefer using a config file. The file needs to start with a section marker `[flake8]` and the following options are then parsed using flake8's config parser, and can be used just like any other flake8 options.
149
150
  Note that it's not currently possible to use a configuration file when running `flake8-async` standalone.
150
151
 
152
+ ### `ValueError` when trying to `ignore` error codes in config file
153
+ Error codes with more than three letters are not possible to `ignore` in config files since flake8>=6, as flake8 tries to validate correct configuration with a regex. We have decided not to conform to this, as it would be a breaking change for end-users requiring them to update `noqa`s and configurations, we think the `ASYNC` code is much more readable than e.g. `ASYxxx`, and ruff does not enforce such a limit. The easiest option for users hitting this error is to instead use the `--disable` option as documented [below](#--disable). See further discussion and other workarounds in https://github.com/python-trio/flake8-async/issues/230
154
+
151
155
  ### `--enable`
152
156
  Comma-separated list of error codes to enable, similar to flake8 --select but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors.
153
157
 
@@ -221,6 +225,12 @@ async def my_function():
221
225
  # Changelog
222
226
  *[CalVer, YY.month.patch](https://calver.org/)*
223
227
 
228
+ ## 24.4.2
229
+ - Add ASYNC119: yield in contextmanager in async generator.
230
+
231
+ ## 24.4.1
232
+ - ASYNC91X fix internal error caused by multiple `try/except` incorrectly sharing state.
233
+
224
234
  ## 24.3.6
225
235
  - ASYNC100 no longer triggers if a context manager contains a `yield`.
226
236
 
@@ -41,6 +41,7 @@ pip install flake8-async
41
41
  - **ASYNC115**: Replace `[trio/anyio].sleep(0)` with the more suggestive `[trio/anyio].lowlevel.checkpoint()`.
42
42
  - **ASYNC116**: `[trio/anyio].sleep()` with >24 hour interval should usually be `[trio/anyio].sleep_forever()`.
43
43
  - **ASYNC118**: Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable, since that breaks linter checks and multi-backend programs.
44
+ - **ASYNC119**: `yield` in context manager in async generator is unsafe, the cleanup may be delayed until `await` is no longer allowed. We strongly encourage you to read PEP-533 and use `async with aclosing(...)`, or better yet avoid async generators entirely (see ASYNC900) in favor of context managers which return an iterable channel/queue.
44
45
 
45
46
  ### Warnings for blocking sync calls in async functions
46
47
  Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
@@ -84,7 +85,7 @@ adding the following to your `.pre-commit-config.yaml`:
84
85
  minimum_pre_commit_version: '2.9.0'
85
86
  repos:
86
87
  - repo: https://github.com/python-trio/flake8-async
87
- rev: 24.3.6
88
+ rev: 24.4.2
88
89
  hooks:
89
90
  - id: flake8-async
90
91
  # args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
@@ -123,6 +124,9 @@ flake8-async **/*.py
123
124
  but we prefer using a config file. The file needs to start with a section marker `[flake8]` and the following options are then parsed using flake8's config parser, and can be used just like any other flake8 options.
124
125
  Note that it's not currently possible to use a configuration file when running `flake8-async` standalone.
125
126
 
127
+ ### `ValueError` when trying to `ignore` error codes in config file
128
+ Error codes with more than three letters are not possible to `ignore` in config files since flake8>=6, as flake8 tries to validate correct configuration with a regex. We have decided not to conform to this, as it would be a breaking change for end-users requiring them to update `noqa`s and configurations, we think the `ASYNC` code is much more readable than e.g. `ASYxxx`, and ruff does not enforce such a limit. The easiest option for users hitting this error is to instead use the `--disable` option as documented [below](#--disable). See further discussion and other workarounds in https://github.com/python-trio/flake8-async/issues/230
129
+
126
130
  ### `--enable`
127
131
  Comma-separated list of error codes to enable, similar to flake8 --select but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors.
128
132
 
@@ -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.6"
40
+ __version__ = "24.4.2"
41
41
 
42
42
 
43
43
  # taken from https://github.com/Zac-HD/shed
@@ -454,7 +454,7 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
454
454
  def visit_Try(self, node: cst.Try):
455
455
  if not self.async_function:
456
456
  return
457
- self.save_state(node, "try_state")
457
+ self.save_state(node, "try_state", copy=True)
458
458
  # except & finally guaranteed to enter with checkpoint if checkpointed
459
459
  # before try and no yield in try body.
460
460
  self.try_state.body_uncheckpointed_statements = (
@@ -596,6 +596,8 @@ class Visitor91X(Flake8AsyncVisitor_cst, CommonVisitors):
596
596
  self.save_state(
597
597
  node,
598
598
  "uncheckpointed_statements",
599
+ # reference is overwritten below so don't need to copy
600
+ copy=False,
599
601
  )
600
602
 
601
603
  # inject an artificial uncheckpointed statement that won't raise an error,
@@ -281,6 +281,45 @@ class Visitor116(Flake8AsyncVisitor):
281
281
  self.error(node, m[2])
282
282
 
283
283
 
284
+ @error_class
285
+ class Visitor119(Flake8AsyncVisitor):
286
+ error_codes: Mapping[str, str] = {
287
+ "ASYNC119": "Yield in contextmanager in async generator might not trigger"
288
+ " cleanup. Use `@asynccontextmanager` or refactor."
289
+ }
290
+
291
+ def __init__(self, *args: Any, **kwargs: Any):
292
+ super().__init__(*args, **kwargs)
293
+ self.unsafe_function: bool = False
294
+ self.contextmanager: bool = False
295
+
296
+ def visit_AsyncFunctionDef(
297
+ self, node: ast.AsyncFunctionDef | ast.FunctionDef | ast.Lambda
298
+ ):
299
+ self.save_state(node, "unsafe_function", "contextmanager")
300
+ self.contextmanager = False
301
+ if isinstance(node, ast.AsyncFunctionDef) and not has_decorator(
302
+ node, "asynccontextmanager"
303
+ ):
304
+ self.unsafe_function = True
305
+ else:
306
+ self.unsafe_function = False
307
+
308
+ def visit_With(self, node: ast.With | ast.AsyncWith):
309
+ self.save_state(node, "contextmanager")
310
+ self.contextmanager = True
311
+
312
+ def visit_Yield(self, node: ast.Yield):
313
+ if self.unsafe_function and self.contextmanager:
314
+ self.error(node)
315
+
316
+ visit_AsyncWith = visit_With
317
+ visit_FunctionDef = visit_AsyncFunctionDef
318
+ # it's not possible to yield or open context managers in lambda's, so this
319
+ # one isn't strictly needed afaik.
320
+ visit_Lambda = visit_AsyncFunctionDef
321
+
322
+
284
323
  @error_class
285
324
  @disabled_by_default
286
325
  class Visitor900(Flake8AsyncVisitor):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: flake8-async
3
- Version: 24.3.6
3
+ Version: 24.4.2
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
@@ -66,6 +66,7 @@ pip install flake8-async
66
66
  - **ASYNC115**: Replace `[trio/anyio].sleep(0)` with the more suggestive `[trio/anyio].lowlevel.checkpoint()`.
67
67
  - **ASYNC116**: `[trio/anyio].sleep()` with >24 hour interval should usually be `[trio/anyio].sleep_forever()`.
68
68
  - **ASYNC118**: Don't assign the value of `anyio.get_cancelled_exc_class()` to a variable, since that breaks linter checks and multi-backend programs.
69
+ - **ASYNC119**: `yield` in context manager in async generator is unsafe, the cleanup may be delayed until `await` is no longer allowed. We strongly encourage you to read PEP-533 and use `async with aclosing(...)`, or better yet avoid async generators entirely (see ASYNC900) in favor of context managers which return an iterable channel/queue.
69
70
 
70
71
  ### Warnings for blocking sync calls in async functions
71
72
  Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
@@ -109,7 +110,7 @@ adding the following to your `.pre-commit-config.yaml`:
109
110
  minimum_pre_commit_version: '2.9.0'
110
111
  repos:
111
112
  - repo: https://github.com/python-trio/flake8-async
112
- rev: 24.3.6
113
+ rev: 24.4.2
113
114
  hooks:
114
115
  - id: flake8-async
115
116
  # args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]
@@ -148,6 +149,9 @@ flake8-async **/*.py
148
149
  but we prefer using a config file. The file needs to start with a section marker `[flake8]` and the following options are then parsed using flake8's config parser, and can be used just like any other flake8 options.
149
150
  Note that it's not currently possible to use a configuration file when running `flake8-async` standalone.
150
151
 
152
+ ### `ValueError` when trying to `ignore` error codes in config file
153
+ Error codes with more than three letters are not possible to `ignore` in config files since flake8>=6, as flake8 tries to validate correct configuration with a regex. We have decided not to conform to this, as it would be a breaking change for end-users requiring them to update `noqa`s and configurations, we think the `ASYNC` code is much more readable than e.g. `ASYxxx`, and ruff does not enforce such a limit. The easiest option for users hitting this error is to instead use the `--disable` option as documented [below](#--disable). See further discussion and other workarounds in https://github.com/python-trio/flake8-async/issues/230
154
+
151
155
  ### `--enable`
152
156
  Comma-separated list of error codes to enable, similar to flake8 --select but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors.
153
157
 
@@ -221,6 +225,12 @@ async def my_function():
221
225
  # Changelog
222
226
  *[CalVer, YY.month.patch](https://calver.org/)*
223
227
 
228
+ ## 24.4.2
229
+ - Add ASYNC119: yield in contextmanager in async generator.
230
+
231
+ ## 24.4.1
232
+ - ASYNC91X fix internal error caused by multiple `try/except` incorrectly sharing state.
233
+
224
234
  ## 24.3.6
225
235
  - ASYNC100 no longer triggers if a context manager contains a `yield`.
226
236
 
@@ -83,6 +83,7 @@ tests/eval_files/async114.py
83
83
  tests/eval_files/async115.py
84
84
  tests/eval_files/async116.py
85
85
  tests/eval_files/async118.py
86
+ tests/eval_files/async119.py
86
87
  tests/eval_files/async200.py
87
88
  tests/eval_files/async210.py
88
89
  tests/eval_files/async211.py
@@ -601,3 +601,16 @@ async def await_in_gen_target():
601
601
  async def await_everywhere_except_gen_target(): # error: 0, "exit", Statement("function definition", lineno)
602
602
  (await x async for x in bar())
603
603
  await trio.lowlevel.checkpoint()
604
+
605
+
606
+ # Issue #226
607
+ async def fn_226(): # error: 0, "exit", Statement("function definition", lineno)
608
+ try:
609
+ while foo():
610
+ try:
611
+ await trio.sleep(1)
612
+ except Exception:
613
+ pass
614
+ except Exception:
615
+ pass
616
+ await trio.lowlevel.checkpoint()
@@ -0,0 +1,64 @@
1
+ import contextlib
2
+
3
+ from contextlib import asynccontextmanager
4
+
5
+
6
+ async def unsafe_yield():
7
+ with open(""):
8
+ yield # error: 8
9
+
10
+
11
+ async def async_with():
12
+ async with unsafe_yield():
13
+ yield # error: 8
14
+
15
+
16
+ async def warn_on_yeach_yield():
17
+ with open(""):
18
+ yield # error: 8
19
+ yield # error: 8
20
+ with open(""):
21
+ yield # error: 8
22
+ yield # error: 8
23
+
24
+
25
+ async def yield_not_in_contextmanager():
26
+ yield
27
+ with open(""):
28
+ ...
29
+ yield
30
+
31
+
32
+ async def yield_in_nested_function():
33
+ with open(""):
34
+
35
+ def foo():
36
+ yield
37
+
38
+
39
+ async def yield_in_nested_async_function():
40
+ with open(""):
41
+
42
+ async def foo():
43
+ yield
44
+
45
+
46
+ async def yield_after_nested_async_function():
47
+ with open(""):
48
+
49
+ async def foo():
50
+ yield
51
+
52
+ yield # error: 8
53
+
54
+
55
+ @asynccontextmanager
56
+ async def safe_in_contextmanager():
57
+ with open(""):
58
+ yield
59
+
60
+
61
+ @contextlib.asynccontextmanager
62
+ async def safe_in_contextmanager2():
63
+ with open(""):
64
+ yield
@@ -572,3 +572,15 @@ async def await_in_gen_target():
572
572
 
573
573
  async def await_everywhere_except_gen_target(): # error: 0, "exit", Statement("function definition", lineno)
574
574
  (await x async for x in bar())
575
+
576
+
577
+ # Issue #226
578
+ async def fn_226(): # error: 0, "exit", Statement("function definition", lineno)
579
+ try:
580
+ while foo():
581
+ try:
582
+ await trio.sleep(1)
583
+ except Exception:
584
+ pass
585
+ except Exception:
586
+ pass
@@ -210,6 +210,7 @@ trio200-blocking-calls =
210
210
  other -> async,
211
211
  sync_fns.* -> the_async_equivalent,
212
212
  select = ASYNC200
213
+ extend-ignore = E
213
214
  """
214
215
  )
215
216
  assert tmp_path.joinpath("example.py").write_text(
@@ -403,3 +404,33 @@ def test_disable_noqa_ast(
403
404
  == "./example.py:1:1: ASYNC106 trio must be imported with `import trio` for the"
404
405
  " linter to work.\n"
405
406
  )
407
+
408
+
409
+ @pytest.mark.xfail(reason="flake8>=6 enforces three-letter error codes in config")
410
+ def test_config_ignore_error_code(tmp_path: Path) -> None:
411
+ assert tmp_path.joinpath(".flake8").write_text(
412
+ """
413
+ [flake8]
414
+ ignore = ASYNC100
415
+ """
416
+ )
417
+ res = subprocess.run(
418
+ ["flake8", "--help"], cwd=tmp_path, capture_output=True, check=False
419
+ )
420
+ assert not res.stderr
421
+ assert res.returncode == 0
422
+
423
+
424
+ # but make sure we can disable selected codes
425
+ def test_config_disable_error_code(tmp_path: Path) -> None:
426
+ # select ASYNC200 and create file that induces ASYNC200
427
+ _test_trio200_from_config_common(tmp_path)
428
+ # disable ASYNC200
429
+ with open(tmp_path.joinpath(".flake8"), "a", encoding="utf-8") as file:
430
+ file.write("disable = ASYNC200")
431
+
432
+ # it now returns no errors
433
+ res = subprocess.run(["flake8"], cwd=tmp_path, capture_output=True, check=True)
434
+ assert not res.stdout
435
+ assert not res.stderr
436
+ assert res.returncode == 0
@@ -416,6 +416,7 @@ def _parse_eval_file(
416
416
  msg = f"Line {lineno}: Failed to format\n {message!r}\nwith\n{args}"
417
417
  raise ParseError(msg) from e
418
418
 
419
+ assert enabled_codes, "no codes enabled. Fix file name or add `# ARGS --enable=...`"
419
420
  enabled_codes_list = enabled_codes.split(",")
420
421
  for code in enabled_codes_list:
421
422
  assert re.fullmatch(
@@ -20,6 +20,18 @@ deps =
20
20
  commands =
21
21
  pytest {posargs:-n auto}
22
22
 
23
+ [testenv:docs]
24
+ description = Generate docs locally
25
+ deps =
26
+ sphinx
27
+ readthedocs-sphinx-ext
28
+ -r docs/requirements.txt
29
+ allowlist_externals = make
30
+ changedir = docs
31
+ skip_install = True
32
+ commands =
33
+ make html
34
+
23
35
  # Settings for other tools
24
36
  [pytest]
25
37
  addopts =
File without changes
File without changes
File without changes
File without changes