Skip to content

Commit 75f6f87

Browse files
authored
Add new check ASYNC251 for time.sleep() (#219)
* Add new check ASYNC251 for time.sleep() * fix tests
1 parent b7c62ce commit 75f6f87

File tree

7 files changed

+44
-6
lines changed

7 files changed

+44
-6
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22
*[CalVer, YY.month.patch](https://calver.org/)*
33

4+
## 24.3.3
5+
- Add ASYNC251: `time.sleep()` in async method.
6+
47
## 24.3.2
58
- Add ASYNC250: blocking sync call `input()` in async method.
69

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.
5555
- **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).
5656
- **ASYNC232**: Blocking sync call on file object, wrap the file object in `[trio/anyio].wrap_file()` to get an async file object.
5757
- **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).
58-
- **ASYNC250**: Builtin `input()` should not be called from async function.
58+
- **ASYNC250**: Builtin `input()` should not be called from async function. Wrap in `[trio/anyio].to_thread.run_sync()` or `asyncio.loop.run_in_executor()`.
59+
- **ASYNC251**: `time.sleep(...)` should not be called from async function. Use `[trio/anyio/asyncio].sleep(...)`.
5960

6061
### Warnings disabled by default
6162
- **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.

flake8_async/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838

3939
# CalVer: YY.month.patch, e.g. first release of July 2022 == "22.7.1"
40-
__version__ = "24.3.2"
40+
__version__ = "24.3.3"
4141

4242

4343
# taken from https://github.com/Zac-HD/shed

flake8_async/visitors/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
utility_visitors: set[type[Flake8AsyncVisitor]] = set()
2626
utility_visitors_cst: set[type[Flake8AsyncVisitor_cst]] = set()
2727

28-
# Import all visitors so their decorators run, filling the above containers
28+
# Import all files with visitors so their decorators run, filling the above containers
2929
# This has to be done at the end to avoid circular imports
3030
from . import (
3131
visitor2xx,

flake8_async/visitors/visitor2xx.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,25 @@ def visit_Call(self, node: ast.Call):
398398
class Visitor25X(Visitor200):
399399
error_codes: Mapping[str, str] = {
400400
"ASYNC250": ("Blocking sync call `input()` in async function. Wrap in `{}`."),
401+
"ASYNC251": (
402+
"Blocking sync call `time.sleep(...)` in async function."
403+
" Use `await {}.sleep(...)`."
404+
),
401405
}
402406

403407
def visit_Call(self, node: ast.Call):
404408
if not self.async_function:
405409
return
406-
if isinstance(node.func, ast.Name) and node.func.id == "input":
410+
func_name = ast.unparse(node.func)
411+
if func_name == "input":
412+
error_code = "ASYNC250"
407413
if len(self.library) == 1:
408-
self.error(node, wrappers[self.library_str])
414+
msg_param = wrappers[self.library_str]
409415
else:
410-
self.error(node, "/".join(wrappers[lib] for lib in self.library))
416+
msg_param = "/".join(wrappers[lib] for lib in self.library)
417+
elif func_name == "time.sleep":
418+
error_code = "ASYNC251"
419+
msg_param = self.library_str
420+
else:
421+
return
422+
self.error(node, msg_param, error_code=error_code)

tests/eval_files/async251.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# NOAUTOFIX
2+
3+
import time
4+
from time import sleep
5+
6+
7+
async def foo():
8+
time.sleep(5) # ASYNC251: 4, "trio"
9+
time.sleep(5) if 5 else time.sleep(5) # ASYNC251: 4, "trio" # ASYNC251: 28, "trio"
10+
11+
# Not handled due to difficulty tracking imports and not wanting to trigger
12+
# false positives. But could definitely be handled by ruff et al.
13+
sleep(5)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# BASE_LIBRARY trio
2+
# NOASYNCIO # tests asyncio without replacing for it
3+
import trio
4+
import time
5+
import asyncio
6+
7+
8+
async def foo():
9+
time.sleep(5) # ASYNC251: 4, "[trio/asyncio]"

0 commit comments

Comments
 (0)