From 43952116ce19955258b2c615cc156b268301e282 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Fri, 23 May 2025 14:58:20 +0200 Subject: [PATCH 1/2] also check for kwargs in async115 and async116 --- docs/changelog.rst | 4 ++ docs/usage.rst | 2 +- flake8_async/__init__.py | 2 +- flake8_async/visitors/visitors.py | 67 ++++++++++++++++++------------- tests/eval_files/async115.py | 13 ++++++ tests/eval_files/async116.py | 6 +++ 6 files changed, 65 insertions(+), 29 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9f8cc5ec..26b10c57 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changelog `CalVer, YY.month.patch `_ +25.5.3 +====== +- :ref:`ASYNC115 ` and :ref:`ASYNC116 ` now also checks kwargs. + 25.5.2 ====== - :ref:`ASYNC102 ` and :ref:`ASYNC120 ` no longer requires cancel scopes to have a timeout. `(issue #272) `_ diff --git a/docs/usage.rst b/docs/usage.rst index a3101c5b..b1bb6c23 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -33,7 +33,7 @@ adding the following to your ``.pre-commit-config.yaml``: minimum_pre_commit_version: '2.9.0' repos: - repo: https://github.com/python-trio/flake8-async - rev: 25.5.2 + rev: 25.5.3 hooks: - id: flake8-async # args: ["--enable=ASYNC100,ASYNC112", "--disable=", "--autofix=ASYNC"] diff --git a/flake8_async/__init__.py b/flake8_async/__init__.py index 15583f6a..c0ba7b3a 100644 --- a/flake8_async/__init__.py +++ b/flake8_async/__init__.py @@ -38,7 +38,7 @@ # CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1" -__version__ = "25.5.2" +__version__ = "25.5.3" # taken from https://github.com/Zac-HD/shed diff --git a/flake8_async/visitors/visitors.py b/flake8_async/visitors/visitors.py index f77f2e62..e4fa1c83 100644 --- a/flake8_async/visitors/visitors.py +++ b/flake8_async/visitors/visitors.py @@ -305,11 +305,16 @@ class Visitor115(Flake8AsyncVisitor): } def visit_Call(self, node: ast.Call): + if not (m := get_matching_call(node, "sleep")): + return if ( - (m := get_matching_call(node, "sleep")) - and len(node.args) == 1 + len(node.args) == 1 and isinstance(node.args[0], ast.Constant) and node.args[0].value == 0 + ) or ( + len(node.keywords) == 1 + and isinstance(node.keywords[0].value, ast.Constant) + and node.keywords[0].value.value == 0 ): self.error(node, m.base) @@ -324,32 +329,40 @@ class Visitor116(Flake8AsyncVisitor): } def visit_Call(self, node: ast.Call): - if (m := get_matching_call(node, "sleep")) and len(node.args) == 1: + if not (m := get_matching_call(node, "sleep")): + return + if len(node.args) == 1: arg = node.args[0] - if ( - # `trio.sleep(math.inf)` - (isinstance(arg, ast.Attribute) and arg.attr == "inf") - # `trio.sleep(inf)` - or (isinstance(arg, ast.Name) and arg.id == "inf") - # `trio.sleep(float("inf"))` - or ( - isinstance(arg, ast.Call) - and isinstance(arg.func, ast.Name) - and arg.func.id == "float" - and len(arg.args) - and isinstance(arg.args[0], ast.Constant) - and arg.args[0].value == "inf" - ) - # `trio.sleep(1e999)` (constant value inf) - # `trio.sleep(86401)` - # `trio.sleep(86400.1)` - or ( - isinstance(arg, ast.Constant) - and isinstance(arg.value, (int, float)) - and arg.value > 86400 - ) - ): - self.error(node, m.base) + elif len(node.keywords) == 1: + arg = node.keywords[0].value + else: + # invalid call, not our problem + return + + if ( + # `trio.sleep(math.inf)` + (isinstance(arg, ast.Attribute) and arg.attr == "inf") + # `trio.sleep(inf)` + or (isinstance(arg, ast.Name) and arg.id == "inf") + # `trio.sleep(float("inf"))` + or ( + isinstance(arg, ast.Call) + and isinstance(arg.func, ast.Name) + and arg.func.id == "float" + and len(arg.args) + and isinstance(arg.args[0], ast.Constant) + and arg.args[0].value == "inf" + ) + # `trio.sleep(1e999)` (constant value inf) + # `trio.sleep(86401)` + # `trio.sleep(86400.1)` + or ( + isinstance(arg, ast.Constant) + and isinstance(arg.value, (int, float)) + and arg.value > 86400 + ) + ): + self.error(node, m.base) @error_class diff --git a/tests/eval_files/async115.py b/tests/eval_files/async115.py index fbfa7d9a..05dd7586 100644 --- a/tests/eval_files/async115.py +++ b/tests/eval_files/async115.py @@ -22,6 +22,19 @@ async def afoo(): time.sleep(0) sleep(0) + # in trio it's called 'seconds', in anyio it's 'delay', but + # we don't care about the kwarg name. #382 + await trio.sleep(seconds=0) # error: 10, "trio" + await trio.sleep(delay=0) # error: 10, "trio" + await trio.sleep(anything=0) # error: 10, "trio" + + await trio.sleep(seconds=1) + + await trio.sleep() + + # we don't care to suppress this + await trio.sleep(0, seconds=1) # error: 10, "trio" + # don't require being inside a function trio.sleep(0) # error: 0, "trio" diff --git a/tests/eval_files/async116.py b/tests/eval_files/async116.py index 60bf8b93..46029224 100644 --- a/tests/eval_files/async116.py +++ b/tests/eval_files/async116.py @@ -42,6 +42,12 @@ async def foo(): await trio.sleep(inf.inf) # error: 10, "trio" await trio.sleep(inf.anything) + # in trio the kwarg name is 'seconds', in anyio it's 'delay', but + # we error regardless of what it is. #382 + await trio.sleep(seconds=inf) # error: 10, "trio" + await trio.sleep(delay=inf) # error: 10, "trio" + await trio.sleep(anything=inf) # error: 10, "trio" + # does not require the call to be awaited, nor in an async fun trio.sleep(86401) # error: 0, "trio" From a1597904f7b661e2761f6644a62a6888a2dcbef0 Mon Sep 17 00:00:00 2001 From: jakkdl Date: Mon, 26 May 2025 11:38:55 +0200 Subject: [PATCH 2/2] add ref targets for 115 and 116 --- docs/rules.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rules.rst b/docs/rules.rst index db198f6e..4a640aa4 100644 --- a/docs/rules.rst +++ b/docs/rules.rst @@ -65,13 +65,13 @@ _`ASYNC113` : start-soon-in-aenter _`ASYNC114` : startable-not-in-config Startable function (i.e. has a ``task_status`` keyword parameter) not in :ref:`--startable-in-context-manager <--startable-in-context-manager>` parameter list, please add it so ASYNC113 can catch errors when using it. -ASYNC115 : async-zero-sleep +_`ASYNC115` : async-zero-sleep Replace :func:`trio.sleep(0) `/:func:`anyio.sleep(0) ` with the more suggestive :func:`trio.lowlevel.checkpoint`/:func:`anyio.lowlevel.checkpoint`. -ASYNC116 : long-sleep-not-forever +_`ASYNC116` : long-sleep-not-forever :func:`trio.sleep`/:func:`anyio.sleep` with >24 hour interval should usually be :func:`trio.sleep_forever`/:func:`anyio.sleep_forever`. -ASYNC118 : cancelled-class-saved +_`ASYNC118` : cancelled-class-saved Don't assign the value of :func:`anyio.get_cancelled_exc_class` to a variable, since that breaks linter checks and multi-backend programs. _`ASYNC119` : yield-in-cm-in-async-gen