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
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ use ruff_python_semantic::analyze::typing::{TypeChecker, check_type, traverse_un
use ruff_text_size::Ranged;

/// ## What it does
/// Checks that async functions do not call blocking `os.path` or `pathlib.Path`
/// methods.
/// Checks that async functions do not call blocking `os.path` functions or
/// `pathlib.Path` methods.
///
/// ## Why is this bad?
/// Calling some `os.path` or `pathlib.Path` methods in an async function will block
/// the entire event loop, preventing it from executing other tasks while waiting
/// for the operation. This negates the benefits of asynchronous programming.
/// Calling some `os.path` functions or `pathlib.Path` methods in an async function
/// will block the entire event loop, preventing it from executing other tasks while
/// waiting for the operation. This negates the benefits of asynchronous programming.
///
/// Instead, use the methods' async equivalents from `trio.Path` or `anyio.Path`.
/// Instead, run blocking path operations in a separate thread, or use an API that
/// provides asynchronous path operations, such as `aiofiles.os.path`, `anyio.Path`,
/// or `trio.Path`.
///
/// ## Example
/// ```python
Expand All @@ -27,26 +29,38 @@ use ruff_text_size::Ranged;
/// file_exists = os.path.exists(path)
/// ```
///
/// Use instead:
/// On Python 3.9 and later, use instead:
/// ```python
/// import trio
/// import asyncio
/// import os
///
///
/// async def func():
/// path = trio.Path("my_file.txt")
/// file_exists = await path.exists()
/// path = "my_file.txt"
/// file_exists = await asyncio.to_thread(os.path.exists, path)
/// ```
///
/// Non-blocking methods are OK to use:
/// On earlier Python versions, use `loop.run_in_executor()`.
///
/// Or, use an asynchronous path API:
/// ```python
/// import pathlib
/// import trio
///
///
/// async def func():
/// path = pathlib.Path("my_file.txt")
/// file_dirname = path.dirname()
/// new_path = os.path.join("/tmp/src/", path)
/// path = trio.Path("my_file.txt")
/// file_exists = await path.exists()
/// ```
///
/// Purely computational path operations, such as `os.path.join()` and
/// `pathlib.Path.with_suffix()`, are not flagged.
///
/// ## References
/// - [Python documentation: `asyncio.to_thread`](https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread)
/// - [Python documentation: `loop.run_in_executor`](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor)
/// - [aiofiles documentation: `aiofiles.os`](https://github.com/Tinche/aiofiles#usage)
/// - [AnyIO documentation: `anyio.Path`](https://anyio.readthedocs.io/en/stable/api.html#anyio.Path)
/// - [Trio documentation: `trio.Path`](https://trio.readthedocs.io/en/stable/reference-io.html#trio.Path)
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.15.0", category = Category::Suspicious)]
pub(crate) struct BlockingPathMethodInAsyncFunction {
Expand All @@ -57,7 +71,7 @@ impl Violation for BlockingPathMethodInAsyncFunction {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"Async functions should not use {path_library} methods, use trio.Path or anyio.path",
"Async functions should not perform blocking {path_library} operations",
path_library = self.path_library
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
---
ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking os.path operations
--> ASYNC240.py:67:5
|
65 | file = "file.txt"
Expand All @@ -11,7 +11,7 @@ ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.
68 | os.path.exists(file) # ASYNC240
|

ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking os.path operations
--> ASYNC240.py:68:5
|
67 | os.path.abspath(file) # ASYNC240
Expand All @@ -21,7 +21,7 @@ ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.
70 | async def pathlib_path_in_foo():
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:72:5
|
70 | async def pathlib_path_in_foo():
Expand All @@ -32,7 +32,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
74 | async def pathlib_path_in_foo():
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:78:5
|
77 | path = pathlib.Path("src/my_text.txt")
Expand All @@ -42,7 +42,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
80 | async def inline_path_method_call():
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:81:5
|
80 | async def inline_path_method_call():
Expand All @@ -51,7 +51,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
82 | Path("src/my_text.txt").absolute().exists() # ASYNC240
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:82:5
|
80 | async def inline_path_method_call():
Expand All @@ -62,7 +62,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
84 | async def aliased_path_in_foo():
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:88:5
|
87 | path = PathAlias("src/my_text.txt")
Expand All @@ -72,7 +72,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
90 | global_path = Path("src/my_text.txt")
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:93:5
|
92 | async def global_path_in_foo():
Expand All @@ -82,7 +82,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
95 | async def path_as_simple_parameter_type(path: Path):
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:96:5
|
95 | async def path_as_simple_parameter_type(path: Path):
Expand All @@ -92,7 +92,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
98 | async def path_as_union_parameter_type(path: Path | None):
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:99:5
|
98 | async def path_as_union_parameter_type(path: Path | None):
Expand All @@ -102,7 +102,7 @@ ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or a
101 | async def path_as_optional_parameter_type(path: Optional[Path]):
|

ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
ASYNC240 Async functions should not perform blocking pathlib.Path operations
--> ASYNC240.py:102:5
|
101 | async def path_as_optional_parameter_type(path: Optional[Path]):
Expand Down
Loading