Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10: add trio[301], don't await trio.sleep inside loop #21

Merged
merged 3 commits into from
Aug 5, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

## Future
- Added TRIO109: Async definitions should not have a `timeout` parameter. Use `trio.[fail/move_on]_[at/after]`
- Added TRIO110: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.

## 22.7.6
- Extend TRIO102 to also check inside `except BaseException` and `except trio.Cancelled`
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ pip install flake8-trio
- **TRIO108**: Early return from async function must have at least one checkpoint on every code path before it, unless an exception is raised.
Checkpoints are `await`, `async with` `async for`.
- **TRIO109**: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead
- **TRIO110**: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`.
15 changes: 15 additions & 0 deletions flake8_trio.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def visit_AsyncWith(self, node: ast.AsyncWith):
def visit_FunctionDef(self, node: Union[ast.FunctionDef, ast.AsyncFunctionDef]):
outer = self.get_state()
self._yield_is_error = False
self._inside_loop = False

# check for @<context_manager_name> and @<library>.<context_manager_name>
if has_decorator(node.decorator_list, *context_manager_names):
Expand Down Expand Up @@ -198,6 +199,19 @@ def check_109(self, args: ast.arguments):
if arg.arg == "timeout":
self.error(TRIO109, arg.lineno, arg.col_offset)

def visit_While(self, node: ast.While):
self.check_for_110(node)
self.generic_visit(node)

def check_for_110(self, node: ast.While):
if (
len(node.body) == 1
and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, ast.Await)
and get_trio_scope(node.body[0].value.value, "sleep", "sleep_until")
):
self.error(TRIO110, node.lineno, node.col_offset)


def critical_except(node: ast.ExceptHandler) -> Optional[Tuple[int, int, str]]:
def has_exception(node: Optional[ast.expr]) -> str:
Expand Down Expand Up @@ -639,3 +653,4 @@ def run(self) -> Iterable[Error]:
TRIO107 = "TRIO107: Async functions must have at least one checkpoint on every code path, unless an exception is raised"
TRIO108 = "TRIO108: Early return from async function must have at least one checkpoint on every code path before it."
TRIO109 = "TRIO109: Async function definition with a `timeout` parameter - use `trio.[fail/move_on]_[after/at]` instead"
TRIO110 = "TRIO110: `while <condition>: await trio.sleep()` should be replaced by a `trio.Event`."
File renamed without changes.
70 changes: 70 additions & 0 deletions tests/trio110.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import trio
import trio as noerror


async def foo():
# only trigger on while loop with body being exactly one sleep[_until] statement
while ...: # error: 4
await trio.sleep()

while ...: # error: 4
await trio.sleep_until()

# nested

while ...: # safe
while ...: # error: 8
await trio.sleep()
await trio.sleep()

while ...: # safe
while ...: # error: 8
await trio.sleep()

### the rest are all safe

# don't trigger on bodies with more than one statement
while ...:
await trio.sleep()
await trio.sleep()

while ...: # safe
...
await trio.sleep()

while ...:
await trio.sleep()
await trio.sleep_until()

# check library name
while ...:
await noerror.sleep()

async def sleep():
...

while ...:
await sleep()

# check function name
while ...:
await trio.sleepies()

# don't trigger on [async] for
for _ in "":
await trio.sleep()

async for _ in trio.blah:
await trio.sleep()

while ...:

async def blah():
await trio.sleep()

while ...:
if ...:
await trio.sleep()

while await trio.sleep():
...