From f4ab11ba5c1e4eaaa396d5f43886bfc9c7682a58 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 27 Jan 2023 12:51:33 +0100
Subject: [PATCH] Small followup fixes and changes to 114 & 116 after the file
split
---
CHANGELOG.md | 4 ++++
README.md | 2 +-
flake8_trio/__init__.py | 4 ++--
flake8_trio/base.py | 2 ++
flake8_trio/visitors/visitors.py | 18 ++++++------------
setup.py | 2 +-
tests/eval_files/trio114.py | 3 ++-
tests/eval_files/trio116.py | 8 ++++++++
tests/test_all_visitors_imported.py | 23 +++++++++++++++++++++++
tests/test_decorator.py | 4 ++--
tests/test_flake8_trio.py | 2 +-
11 files changed, 52 insertions(+), 20 deletions(-)
create mode 100644 tests/test_all_visitors_imported.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3936927f..8976aa48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
# Changelog
*[CalVer, YY.month.patch](https://calver.org/)*
+## 23.1.4
+- TRIO114 no longer triggers on posonly args named "task_status"
+- TRIO116 will now match on any attribute parameter named `.inf`, not just `math.inf`.
+
## 23.1.3
- Add TRIO240: usage of `os.path` in async function.
- Add TRIO900: ban async generators not decorated with known safe decorator
diff --git a/README.md b/README.md
index d1570ee5..df41a292 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ pip install flake8-trio
- **TRIO111**: Variable, from context manager opened inside nursery, passed to `start[_soon]` might be invalidly accesed 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.
- **TRIO112**: 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.
- **TRIO113**: using `nursery.start_soon` in `__aenter__` doesn't wait for the task to begin. Consider replacing with `nursery.start`.
-- **TRIO114**: Startable function (i.e. has a `task_status` parameter) not in `--startable-in-context-manager` parameter list, please add it so TRIO113 can catch errors when using it.
+- **TRIO114**: Startable function (i.e. has a `task_status` keyword parameter) not in `--startable-in-context-manager` parameter list, please add it so TRIO113 can catch errors when using it.
- **TRIO115**: Replace `trio.sleep(0)` with the more suggestive `trio.lowlevel.checkpoint()`.
- **TRIO116**: `trio.sleep()` with >24 hour interval should usually be`trio.sleep_forever()`.
- **TRIO200**: User-configured error for blocking sync calls in async functions. Does nothing by default, see [`trio200-blocking-calls`](#trio200-blocking-calls) for how to configure it.
diff --git a/flake8_trio/__init__.py b/flake8_trio/__init__.py
index 5b3a8b11..b8029658 100644
--- a/flake8_trio/__init__.py
+++ b/flake8_trio/__init__.py
@@ -31,7 +31,7 @@
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
-__version__ = "23.1.3"
+__version__ = "23.1.4"
class Plugin:
@@ -92,7 +92,7 @@ def add_options(option_manager: OptionManager):
parse_from_config=True,
required=False,
help=(
- "Comma-separated list of key:value pairs, where key is a [dotted] "
+ "Comma-separated list of key->value pairs, where key is a [dotted] "
"function that if found inside an async function will raise TRIO200, "
"suggesting it be replaced with {value}"
),
diff --git a/flake8_trio/base.py b/flake8_trio/base.py
index 1daeaca3..fda46fcd 100644
--- a/flake8_trio/base.py
+++ b/flake8_trio/base.py
@@ -36,6 +36,8 @@ def __iter__(self):
yield self.line
yield self.col
yield f"{self.code} " + self.message.format(*self.args)
+ # We are no longer yielding `type(Plugin)` since that's quite tricky to do
+ # without circular imports, and afaik flake8 doesn't care anymore.
yield None
def cmp(self):
diff --git a/flake8_trio/visitors/visitors.py b/flake8_trio/visitors/visitors.py
index 861e0389..d2dd94ff 100644
--- a/flake8_trio/visitors/visitors.py
+++ b/flake8_trio/visitors/visitors.py
@@ -23,7 +23,8 @@
class Visitor100(Flake8TrioVisitor):
error_codes = {
"TRIO100": (
- "{} context contains no checkpoints, add `await trio.lowlevel.checkpoint()`"
+ "{} context contains no checkpoints, remove the context or add"
+ " `await trio.lowlevel.checkpoint()`"
),
}
@@ -255,8 +256,8 @@ def _get_identifier(node: ast.expr) -> str:
class Visitor113(Flake8TrioVisitor):
error_codes = {
"TRIO113": (
- "Dangerous `.start_soon()`, process might not run before `__aenter__` "
- "exits. Consider replacing with `.start()`."
+ "Dangerous `.start_soon()`, function might not be executed before"
+ " `__aenter__` exits. Consider replacing with `.start()`."
),
}
@@ -315,9 +316,7 @@ class Visitor114(Flake8TrioVisitor):
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef):
if any(
isinstance(n, ast.arg) and n.arg == "task_status"
- for n in self.walk(
- *node.args.args, *node.args.posonlyargs, *node.args.kwonlyargs
- )
+ for n in self.walk(*node.args.args, *node.args.kwonlyargs)
) and not any(
node.name == opt
for opt in (*self.options.startable_in_context_manager, *STARTABLE_CALLS)
@@ -357,12 +356,7 @@ def visit_Call(self, node: ast.Call):
arg = node.args[0]
if (
# `trio.sleep(math.inf)`
- (
- isinstance(arg, ast.Attribute)
- and isinstance(arg.value, ast.Name)
- and arg.attr == "inf"
- and arg.value.id == "math"
- )
+ (isinstance(arg, ast.Attribute) and arg.attr == "inf")
# `trio.sleep(inf)`
or (isinstance(arg, ast.Name) and arg.id == "inf")
# `trio.sleep(float("inf"))`
diff --git a/setup.py b/setup.py
index 7392b97d..03ec4c72 100755
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,7 @@ def local_file(name: str) -> Path:
setup(
name="flake8-trio",
version=__version__,
- author="Zac Hatfield-Dodds and Contributors",
+ author="Zac Hatfield-Dodds, John Litborn, and Contributors",
author_email="zac@zhd.dev",
packages=find_packages(include=["flake8_trio", "flake8_trio.*"]),
url="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/Zac-HD/flake8-trio",
diff --git a/tests/eval_files/trio114.py b/tests/eval_files/trio114.py
index 7c245c83..ea9fcd9b 100644
--- a/tests/eval_files/trio114.py
+++ b/tests/eval_files/trio114.py
@@ -24,7 +24,8 @@ async def foo3(*, task_status): # error: 0, "foo3"
...
-async def foo4(task_status, /): # error: 0, "foo4"
+# don't error on pos-only parameter
+async def foo4(task_status, /):
...
diff --git a/tests/eval_files/trio116.py b/tests/eval_files/trio116.py
index cc1d2ccd..7cf25675 100644
--- a/tests/eval_files/trio116.py
+++ b/tests/eval_files/trio116.py
@@ -12,6 +12,7 @@ async def foo():
await trio.sleep(float("inf")) # error: 10
await trio.sleep(math.inf) # error: 10
await trio.sleep(inf) # error: 10
+
# 'inf literal' overflow trick
await trio.sleep(1e999) # error: 10
@@ -32,6 +33,13 @@ async def foo():
await trio.sleep("hello")
await trio.sleep(...)
+ # don't require inf to be in math #103
+ await trio.sleep(np.inf) # error: 10
+ await trio.sleep(anything.inf) # error: 10
+ await trio.sleep(anything.anything.inf) # error: 10
+ await trio.sleep(inf.inf) # error: 10
+ await trio.sleep(inf.anything)
+
# does not require the call to be awaited, nor in an async fun
trio.sleep(86401) # error: 0
diff --git a/tests/test_all_visitors_imported.py b/tests/test_all_visitors_imported.py
new file mode 100644
index 00000000..7c153192
--- /dev/null
+++ b/tests/test_all_visitors_imported.py
@@ -0,0 +1,23 @@
+"""Check that all visitors are imported.
+
+Checks that all flake8_trio/visitor*.py files are imported in flake8_trio/visitor/__init__
+so their decorators are run.
+"""
+
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+
+def test_all_visitors_imported():
+ visitor_dir = Path(__file__).parent.parent / "flake8_trio" / "visitors"
+ visitor_files = {
+ f.stem for f in visitor_dir.iterdir() if f.stem.startswith("visitor")
+ }
+ visited_files = set()
+ with open(visitor_dir / "__init__.py") as f:
+ for line in f:
+ if m := re.match(r"from \. import (?P\w*)", line):
+ visited_files.add(m.group("module"))
+ assert visitor_files == visited_files
diff --git a/tests/test_decorator.py b/tests/test_decorator.py
index 5cd18ee2..726bb319 100644
--- a/tests/test_decorator.py
+++ b/tests/test_decorator.py
@@ -96,9 +96,9 @@ def test_command_line_1(capfd):
expected_lineno = -1
with open(file_path) as f:
- for lineno, line in enumerate(f):
+ for lineno, line in enumerate(f, start=1):
if line.startswith("async def"):
- expected_lineno = lineno + 1
+ expected_lineno = lineno
break
expected_out = (
diff --git a/tests/test_flake8_trio.py b/tests/test_flake8_trio.py
index a891b569..ffc3795a 100644
--- a/tests/test_flake8_trio.py
+++ b/tests/test_flake8_trio.py
@@ -249,7 +249,7 @@ def print_first_diff(errors: Sequence[Error], expected: Sequence[Error]):
if first_expected_line != first_error_line:
print(
- "First lines with different errors",
+ "\nFirst lines with different errors",
f" actual: {[e.cmp() for e in first_error_line]}",
f"expected: {[e.cmp() for e in first_expected_line]}",
"",