diff --git a/docs/changelog.rst b/docs/changelog.rst index 9f8cc5e..26b10c5 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 a3101c5..b1bb6c2 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 15583f6..c0ba7b3 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 f77f2e6..e4fa1c8 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 fbfa7d9..05dd758 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 60bf8b9..4602922 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"