Skip to content

Commit

Permalink
Add new check ASYNC251 for time.sleep()
Browse files Browse the repository at this point in the history
  • Loading branch information
jakkdl committed Mar 15, 2024
1 parent b7c62ce commit 1e4ad71
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog
*[CalVer, YY.month.patch](https://calver.org/)*

## 24.3.3
- Add ASYNC251: `time.sleep()` in async method.

## 24.3.2
- Add ASYNC250: blocking sync call `input()` in async method.

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
- **ASYNC231**: Sync IO call in async function, use `[trio/anyio].wrap_file(...)`. `asyncio` users need to use a library such as [aiofiles](https://pypi.org/project/aiofiles/), or switch to [anyio](https://github.com/agronholm/anyio).
- **ASYNC232**: Blocking sync call on file object, wrap the file object in `[trio/anyio].wrap_file()` to get an async file object.
- **ASYNC240**: Avoid using `os.path` in async functions, prefer using `[trio/anyio].Path` objects. `asyncio` users should consider [aiopath](https://pypi.org/project/aiopath) or [anyio](https://github.com/agronholm/anyio).
- **ASYNC250**: Builtin `input()` should not be called from async function.
- **ASYNC250**: Builtin `input()` should not be called from async function. Wrap in `[trio/anyio].to_thread.run_sync()` or `asyncio.loop.run_in_executor()`.
- **ASYNC251**: `time.sleep(...)` should not be called from async function. Use `[trio/anyio/asyncio].sleep(...)`.

### Warnings disabled by default
- **ASYNC900**: Async generator without `@asynccontextmanager` not allowed. You might want to enable this on a codebase since async generators are inherently unsafe and cleanup logic might not be performed. See https://github.com/python-trio/flake8-async/issues/211 and https://discuss.python.org/t/using-exceptiongroup-at-anthropic-experience-report/20888/6 for discussion.
Expand Down
2 changes: 1 addition & 1 deletion flake8_async/visitors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
utility_visitors: set[type[Flake8AsyncVisitor]] = set()
utility_visitors_cst: set[type[Flake8AsyncVisitor_cst]] = set()

# Import all visitors so their decorators run, filling the above containers
# Import all files with visitors so their decorators run, filling the above containers
# This has to be done at the end to avoid circular imports
from . import (
visitor2xx,
Expand Down
18 changes: 15 additions & 3 deletions flake8_async/visitors/visitor2xx.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,13 +398,25 @@ def visit_Call(self, node: ast.Call):
class Visitor25X(Visitor200):
error_codes: Mapping[str, str] = {
"ASYNC250": ("Blocking sync call `input()` in async function. Wrap in `{}`."),
"ASYNC251": (
"Blocking sync call `time.sleep(...)` in async function."
" Use `await {}.sleep(...)`."
),
}

def visit_Call(self, node: ast.Call):
if not self.async_function:
return
if isinstance(node.func, ast.Name) and node.func.id == "input":
func_name = ast.unparse(node.func)
if func_name == "input":
error_code = "ASYNC250"
if len(self.library) == 1:
self.error(node, wrappers[self.library_str])
msg_param = wrappers[self.library_str]
else:
self.error(node, "/".join(wrappers[lib] for lib in self.library))
msg_param = "[" + "/".join(wrappers[lib] for lib in self.library) + "]"
elif func_name == "time.sleep":
error_code = "ASYNC251"
msg_param = self.library_str
else:
return
self.error(node, msg_param, error_code=error_code)
13 changes: 13 additions & 0 deletions tests/eval_files/async251.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# NOAUTOFIX

import time
from time import sleep


async def foo():
time.sleep(5) if 5 else None # ASYNC251: 8, "trio"
time.sleep(5) # ASYNC251: 4, "trio"

# Not handled due to difficulty tracking imports and not wanting to trigger
# false positives. But could definitely be handled by ruff et al.
sleep(5)
9 changes: 9 additions & 0 deletions tests/eval_files/async251_multi_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# BASE_LIBRARY trio
# NOASYNCIO # tests asyncio without replacing for it
import trio
import time
import asyncio


async def foo():
time.sleep(5) # ASYNC251: 4, "[trio/asyncio]"

0 comments on commit 1e4ad71

Please sign in to comment.