Skip to content

Commit 9653fde

Browse files
authored
deprecate --trio200-blocking-calls, replacing it with --async200-blocking-calls (#251)
* deprecate --trio200-blocking-calls, replacing it with --async200-blocking-calls * Various small improvements to docs/usage.rst
1 parent c656315 commit 9653fde

File tree

9 files changed

+173
-47
lines changed

9 files changed

+173
-47
lines changed

docs/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Changelog
44

55
*[CalVer, YY.month.patch](https://calver.org/)*
66

7+
24.5.3
8+
======
9+
- Rename config option ``trio200-blocking-calls`` to :ref:`async200-blocking-calls`.
10+
- ``trio200-blocking-calls`` is now deprecated.
11+
712
24.5.2
813
======
914
- ASYNC101 now also warns on anyio & asyncio taskgroups.

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ to likely performance issues, to minor points of idiom that might signal
1616
a misunderstanding.
1717

1818

19-
The plugin may well be too noisy or pedantic depending on your requirements or opinions, in which case you should consider :ref:`--disable` for those rules.
19+
The plugin may well be too noisy or pedantic depending on your requirements or opinions, in which case you should consider :ref:`disable` for those rules.
2020
Pairs well with flake8-bugbear.
2121

2222

docs/rules.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ General rules
1616
- **ASYNC110**: ``while <condition>: await [trio/anyio].sleep()`` should be replaced by a ``[trio/anyio].Event``.
1717
- **ASYNC111**: Variable, from context manager opened inside nursery, passed to ``start[_soon]`` might be invalidly accessed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager.
1818
- **ASYNC112**: Nursery body with only a call to ``nursery.start[_soon]`` and not passing itself as a parameter can be replaced with a regular function call.
19+
20+
.. _async113:
21+
1922
- **ASYNC113**: Using :meth:`trio.Nursery.start_soon` in ``__aenter__`` doesn't wait for the task to begin. Consider replacing with ``nursery.start``.
23+
24+
.. _async114:
25+
2026
- **ASYNC114**: Startable function (i.e. has a ``task_status`` keyword parameter) not in ``--startable-in-context-manager`` parameter list, please add it so ASYNC113 can catch errors when using it.
2127
- **ASYNC115**: Replace ``[trio/anyio].sleep(0)`` with the more suggestive ``[trio/anyio].lowlevel.checkpoint()``.
2228
- **ASYNC116**: ``[trio/anyio].sleep()`` with >24 hour interval should usually be ``[trio/anyio].sleep_forever()``.
@@ -33,6 +39,7 @@ Blocking sync calls in async functions
3339

3440
Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
3541

42+
.. _async200:
3643

3744
- **ASYNC200**: User-configured error for blocking sync calls in async functions. Does nothing by default, see :ref:`async200-blocking-calls` for how to configure it.
3845
- **ASYNC210**: Sync HTTP call in async function, use ``httpx.AsyncClient``. This and the other ASYNC21x checks look for usage of ``urllib3`` and ``httpx.Client``, and recommend using ``httpx.AsyncClient`` as that's the largest http client supporting anyio/trio.
@@ -55,11 +62,26 @@ Optional rules disabled by default
5562
.. _async900:
5663

5764
- **ASYNC900**: Async generator without ``@asynccontextmanager`` not allowed. You might want to enable this on a codebase since async generators are inherently unsafe and cleanup logic might not be performed. See https://github.com/python-trio/flake8-async/issues/211 and https://discuss.python.org/t/using-exceptiongroup-at-anthropic-experience-report/20888/6 for discussion.
65+
66+
.. _async910:
67+
5868
- **ASYNC910**: Exit or ``return`` from async function with no guaranteed checkpoint or exception since function definition. You might want to enable this on a codebase to make it easier to reason about checkpoints, and make the logic of ASYNC911 correct.
69+
70+
.. _async911:
71+
5972
- **ASYNC911**: Exit, ``yield`` or ``return`` from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition)
6073
Checkpoints are ``await``, ``async for``, and ``async with`` (on one of enter/exit).
6174
- **ASYNC912**: A timeout/cancelscope has checkpoints, but they're not guaranteed to run. Similar to ASYNC100, but it does not warn on trivial cases where there is no checkpoint at all. It instead shares logic with ASYNC910 and ASYNC911 for parsing conditionals and branches.
6275

76+
.. _autofix-support:
77+
78+
Autofix support
79+
===============
80+
The following rules support :ref:`autofixing <autofix>`.
81+
- ASYNC100
82+
- ASYNC910
83+
- ASYNC911
84+
6385
Removed rules
6486
================
6587

docs/usage.rst

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
Installation
33
************
44

5-
.. code-block:: console
5+
Install from `PyPI <https://pypi.org/project/flake8-async>`__
6+
7+
.. code-block:: sh
68
79
pip install flake8-async
810
@@ -23,7 +25,7 @@ install and run through flake8
2325
install and run with pre-commit
2426
===============================
2527

26-
If you use `pre-commit <https://pre-commit.com/>`_, you can use it with flake8-async by
28+
If you use `pre-commit <https://pre-commit.com/>`__, you can use it with flake8-async by
2729
adding the following to your ``.pre-commit-config.yaml``:
2830

2931
.. code-block:: yaml
@@ -38,17 +40,21 @@ adding the following to your ``.pre-commit-config.yaml``:
3840
3941
This is often considerably faster for large projects, because ``pre-commit``
4042
can avoid running ``flake8-async`` on unchanged files.
43+
``flake8-async`` does not retain any memory between files, they are parsed completely independently.
44+
4145
Afterwards, run
4246

4347
.. code-block:: sh
4448
4549
pip install pre-commit flake8-async
4650
pre-commit run .
4751
52+
.. _run_standalone:
53+
4854
install and run as standalone
4955
=============================
5056

51-
If inside a git repository, running without arguments will run it against all ``*.py`` files in the repository.
57+
If inside a git repository, running without arguments will run it against all ``*.py`` files in the repository tree.
5258

5359
.. code-block:: sh
5460
@@ -81,7 +87,7 @@ Run through ruff
8187
================
8288
`Ruff <https://github.com/astral-sh/ruff>`_ is a linter and formatter that reimplements a lot of rules from various flake8 plugins.
8389

84-
They currently only support a small subset of the rules though, see https://github.com/astral-sh/ruff/issues/8451 for current status and https://docs.astral.sh/ruff/rules/#flake8-async-async for documentation.
90+
They currently only support a small subset of the ``flake8-async`` rules though, see https://github.com/astral-sh/ruff/issues/8451 for current status and https://docs.astral.sh/ruff/rules/#flake8-async-async for documentation.
8591

8692
*************
8793
Configuration
@@ -105,60 +111,110 @@ config files since flake8>=6, as flake8 tries to validate correct
105111
configuration with a regex. We have decided not to conform to this, as it
106112
would be a breaking change for end-users requiring them to update ``noqa``\ s
107113
and configurations, we think the ``ASYNC`` code is much more readable than
108-
e.g. ``ASYxxx``, and ruff does not enforce such a limit. The easiest option
109-
for users hitting this error is to instead use the ``--disable`` option as
110-
documented `below <#--disable>`__. See further discussion and other
114+
e.g. ``ASYxxx``, and ruff does not enforce such a limit.
115+
The easiest option for users hitting this error is to instead use the :ref:`disable`
116+
option.
117+
See further discussion and other
111118
workarounds in https://github.com/python-trio/flake8-async/issues/230.
112119

120+
.. _enable:
113121

114-
``--enable``
122+
``enable``
115123
------------
116124

117-
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.
125+
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. Defaults to "ASYNC".
126+
127+
Example
128+
^^^^^^^
129+
.. code-block:: none
118130
119-
.. _--disable:
131+
enable=ASYNC1,ASYNC200
120132
121-
``--disable``
133+
.. _disable:
134+
135+
``disable``
122136
-------------
123137

124138
Comma-separated list of error codes to disable, similar to flake8 ``--ignore`` but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors. It will also bypass errors introduced in flake8>=6, see above.
139+
This is parsed after :ref:`enable`, so if a rule is both "enabled" and "disabled" it will be disabled.
140+
Defaults to "ASYNC9".
141+
142+
Example
143+
^^^^^^^
144+
.. code-block:: none
145+
146+
disable=ASYNC91,ASYNC117
125147
126-
``--autofix``
148+
.. _autofix:
149+
150+
``autofix``
127151
-------------
128152

129-
Comma-separated list of error-codes to enable autofixing for if implemented. Requires running as a standalone program. Pass ``--autofix=ASYNC`` to enable all autofixes.
153+
Comma-separated list of error-codes to enable autofixing for if implemented.
154+
Requires :ref:`running as a standalone program <run_standalone>`.
155+
Only a subset of rules support autofixing, see :ref:`this list <autofix-support>`.
156+
Pass ``--autofix=ASYNC`` to enable all available autofixes.
157+
158+
Defaults to an empty list.
130159

160+
Example
161+
^^^^^^^
162+
.. code-block:: none
131163
132-
``--error-on-autofix``
164+
autofix=ASYNC
165+
166+
167+
``error-on-autofix``
133168
----------------------
134169

135-
Whether to also print an error message for autofixed errors.
170+
Whether to also print an error message for autofixed errors. Defaults to ``False``
171+
172+
Example
173+
^^^^^^^
174+
.. code-block:: none
175+
176+
error-on-autofix=True
136177
137178
Modifying rule behaviour
138179
========================
139180

140-
.. _--anyio:
181+
.. _anyio:
141182

142-
``--anyio``
183+
``anyio``
143184
-----------
144185

145-
Change the default library to be anyio instead of trio. If trio is imported it will assume both are available and print suggestions with [anyio/trio].
186+
Change the default library to be anyio instead of trio. This is mostly used for the sake of printing suggestions in error messages, but may affect some logic. If additional libraries are imported other than the default then rules will assume multiple are available simultaneously. It is currently not possible to set multiple default libraries, other than `anyio`+`asyncio`.
187+
188+
Example
189+
^^^^^^^
190+
.. code-block:: none
146191
147-
``--asyncio``
192+
anyio=True
193+
194+
.. _asyncio:
195+
196+
``asyncio``
148197
-------------
149-
Set default library to be ``asyncio``. See :ref:`--anyio`
198+
Set default library to be ``asyncio``. See :ref:`anyio`
199+
200+
Example
201+
^^^^^^^
202+
.. code-block:: none
203+
204+
asyncio=True
150205
151206
152207
``no-checkpoint-warning-decorators``
153208
------------------------------------
154209

155-
Comma-separated list of decorators to disable checkpointing checks for, turning off ASYNC910 and ASYNC911 warnings for functions decorated with any decorator matching any in the list. Matching is done with `fnmatch <https://docs.python.org/3/library/fnmatch.html>`_. Defaults to disabling for ``asynccontextmanager``.
210+
Comma-separated list of decorators to disable checkpointing checks for, turning off :ref:`ASYNC910 <async910>` and :ref:`ASYNC911 <async911>` warnings for functions decorated with any decorator matching any in the list. Matching is done with `fnmatch <https://docs.python.org/3/library/fnmatch.html>`_. Defaults to disabling for ``asynccontextmanager``.
156211

157212
Decorators-to-match must be identifiers or dotted names only (not PEP-614 expressions), and will match against the name only - e.g. ``foo.bar`` matches ``foo.bar``, ``foo.bar()``, and ``foo.bar(args, here)``, etc.
158213

159-
For example:
214+
Example
215+
^^^^^^^
160216

161-
::
217+
.. code-block:: none
162218
163219
no-checkpoint-warning-decorators =
164220
mydecorator,
@@ -171,9 +227,13 @@ For example:
171227

172228
Comma-separated list of methods which should be used with ``.start()`` when opening a context manager,
173229
in addition to the default ``trio.run_process``, ``trio.serve_tcp``, ``trio.serve_ssl_over_tcp``, and
174-
``trio.serve_listeners``. Names must be valid identifiers as per ``str.isidentifier()``. For example:
230+
``trio.serve_listeners``. Names must be valid identifiers as per ``str.isidentifier()``.
231+
Used by :ref:`ASYNC113 <async113>`, and :ref:`ASYNC114 <async114>` will warn when encountering methods not in the list.
232+
233+
Example
234+
^^^^^^^
175235

176-
::
236+
.. code-block:: none
177237
178238
startable-in-context-manager =
179239
myfun,
@@ -182,15 +242,16 @@ in addition to the default ``trio.run_process``, ``trio.serve_tcp``, ``trio.serv
182242
.. _async200-blocking-calls:
183243

184244
``async200-blocking-calls``
185-
---------------------------
245+
-----------------------------
186246

187-
Comma-separated list of pairs of values separated by ``->`` (optional whitespace stripped), where the first is a pattern for a call that should raise an error if found inside an async function, and the second is what should be suggested to use instead. It uses fnmatch as per `no-checkpoint-warning-decorators`_ for matching. The part after ``->`` is not used by the checker other than when printing the error, so you could add extra info there if you want.
247+
Comma-separated list of pairs of values separated by ``->`` (optional whitespace stripped), where the first is a pattern for a call that should raise :ref:`ASYNC200 <async200>` if found inside an async function, and the second is what should be suggested to use instead. It uses fnmatch as per `no-checkpoint-warning-decorators`_ for matching. The part after ``->`` is not used by the checker other than when printing the error, so you could add extra info there if you want.
188248

189249
The format of the error message is ``User-configured blocking sync call {0} in async function, consider replacing with {1}.``, where ``{0}`` is the pattern the call matches and ``{1}`` is the suggested replacement.
190250

191-
Example:
251+
Example
252+
^^^^^^^
192253

193-
::
254+
.. code-block:: none
194255
195256
async200-blocking-calls =
196257
my_blocking_call -> async.alternative,
@@ -201,7 +262,7 @@ Example:
201262
202263
Specified patterns must not have parentheses, and will only match when the pattern is the name of a call, so given the above configuration
203264

204-
::
265+
.. code-block:: python
205266
206267
async def my_function():
207268
my_blocking_call() # this would raise an error

flake8_async/__init__.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import subprocess
1919
import sys
2020
import tokenize
21+
import warnings
2122
from argparse import ArgumentParser, ArgumentTypeError, Namespace
2223
from typing import TYPE_CHECKING
2324

@@ -37,7 +38,7 @@
3738

3839

3940
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
40-
__version__ = "24.5.2"
41+
__version__ = "24.5.3"
4142

4243

4344
# taken from https://github.com/Zac-HD/shed
@@ -262,7 +263,7 @@ def add_options(option_manager: OptionManager | ArgumentParser):
262263
)
263264
add_argument(
264265
"--startable-in-context-manager",
265-
type=parse_trio114_identifiers,
266+
type=parse_async114_identifiers,
266267
default="",
267268
required=False,
268269
help=(
@@ -276,7 +277,18 @@ def add_options(option_manager: OptionManager | ArgumentParser):
276277
)
277278
add_argument(
278279
"--trio200-blocking-calls",
279-
type=parse_trio200_dict,
280+
type=parse_async200_dict,
281+
default={},
282+
required=False,
283+
help=(
284+
"Comma-separated list of key->value pairs, where key is a [dotted] "
285+
"function that if found inside an async function will raise ASYNC200, "
286+
"suggesting it be replaced with {value}"
287+
),
288+
)
289+
add_argument(
290+
"--async200-blocking-calls",
291+
type=parse_async200_dict,
280292
default={},
281293
required=False,
282294
help=(
@@ -348,13 +360,27 @@ def get_matching_codes(
348360
code for code in options.enable if not error_has_subidentifier(code)
349361
)
350362

363+
# we do not use DeprecationWarning, since that is silenced when run as standalone
364+
# or should maybe just print on stderr
365+
if options.trio200_blocking_calls:
366+
warnings.warn(
367+
"trio200-blocking-calls has been deprecated in favor "
368+
"of async200-blocking-calls",
369+
stacklevel=1,
370+
)
371+
assert not options.async200_blocking_calls, (
372+
"You cannot specify both trio200-blocking-calls and "
373+
"async200-blocking-calls. You should only use the latter."
374+
)
375+
options.async200_blocking_calls = options.trio200_blocking_calls
376+
351377
Plugin._options = Options(
352378
enabled_codes=enabled_codes,
353379
autofix_codes=autofix_codes,
354380
error_on_autofix=options.error_on_autofix,
355381
no_checkpoint_warning_decorators=options.no_checkpoint_warning_decorators,
356382
startable_in_context_manager=options.startable_in_context_manager,
357-
trio200_blocking_calls=options.trio200_blocking_calls,
383+
async200_blocking_calls=options.async200_blocking_calls,
358384
anyio=options.anyio,
359385
asyncio=options.asyncio,
360386
disable_noqa=options.disable_noqa,
@@ -365,15 +391,15 @@ def comma_separated_list(raw_value: str) -> list[str]:
365391
return [s.strip() for s in raw_value.split(",") if s.strip()]
366392

367393

368-
def parse_trio114_identifiers(raw_value: str) -> list[str]:
394+
def parse_async114_identifiers(raw_value: str) -> list[str]:
369395
values = comma_separated_list(raw_value)
370396
for value in values:
371397
if keyword.iskeyword(value) or not value.isidentifier():
372398
raise ArgumentTypeError(f"{value!r} is not a valid method identifier")
373399
return values
374400

375401

376-
def parse_trio200_dict(raw_value: str) -> dict[str, str]:
402+
def parse_async200_dict(raw_value: str) -> dict[str, str]:
377403
res: dict[str, str] = {}
378404
splitter = "->" # avoid ":" because it's part of .ini file syntax
379405
values = [s.strip() for s in raw_value.split(",") if s.strip()]

flake8_async/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Options:
3030
error_on_autofix: bool
3131
no_checkpoint_warning_decorators: Collection[str]
3232
startable_in_context_manager: Collection[str]
33-
trio200_blocking_calls: dict[str, str]
33+
async200_blocking_calls: dict[str, str]
3434
anyio: bool
3535
asyncio: bool
3636
disable_noqa: bool

0 commit comments

Comments
 (0)