Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions flake8_trio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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}"
),
Expand Down
2 changes: 2 additions & 0 deletions flake8_trio/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
18 changes: 6 additions & 12 deletions flake8_trio/visitors/visitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()`"
),
}

Expand Down Expand Up @@ -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()`."
),
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"))`
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion tests/eval_files/trio114.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, /):
...


Expand Down
8 changes: 8 additions & 0 deletions tests/eval_files/trio116.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
23 changes: 23 additions & 0 deletions tests/test_all_visitors_imported.py
Original file line number Diff line number Diff line change
@@ -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<module>\w*)", line):
visited_files.add(m.group("module"))
assert visitor_files == visited_files
4 changes: 2 additions & 2 deletions tests/test_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
2 changes: 1 addition & 1 deletion tests/test_flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]}",
"",
Expand Down