Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ jobs:
V: ${{ matrix.python-version }}
run: "uv run --group tox tox -e py$(echo $V | tr -d . | sed 's/^py//')"

- name: "Lint"
- name: "Check code"
if: matrix.python-version == '3.13' && runner.os == 'Linux'
run: "uv run --group tox tox -e lint"
run: "uv run --group tox tox -e check"

- name: "Upload coverage data"
uses: "actions/upload-artifact@v4"
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 25.1.0 (UNRELEASED)

- Call the `asyncio.to_thread` int the `wrap` decorator.
[#213](https://github.com/Tinche/aiofiles/pull/213)
- Switch to [uv](https://docs.astral.sh/uv/) + add Python v3.14 support.
([#219](https://github.com/Tinche/aiofiles/pull/219))
- Add `ruff` formatter and linter.
Expand Down
42 changes: 30 additions & 12 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
src_dir := "src"
tests_dir := "tests"
code_dirs := "src" + " " + tests_dir
run_prefix := if env_var_or_default("VIRTUAL_ENV", "") == "" { "uv run " } else { "" }
code_dirs := src_dir + " " + tests_dir

# https://just.systems/man/en/functions.html#environment-variables
run := if env("VIRTUAL_ENV", "") == "" { "uv run " } else { "" }

# list available rules
default:
just --list

# build the project
build:
uv build

# install dependencies
sync:
uv sync --group lint --group test --group tox

# check the code
check:
{{ run_prefix }}ruff format --check {{ code_dirs }}
{{ run_prefix }}ruff check {{ code_dirs }}
{{ run }}ruff format --check {{ code_dirs }}
{{ run }}ruff check {{ code_dirs }}
{{ run }}mypy {{ src_dir }} # lint only the source code

# run coverage
coverage:
{{ run_prefix }}coverage run -m pytest {{ tests_dir }}

format:
{{ run_prefix }}ruff format {{ code_dirs }}
{{ run }}coverage run -m pytest {{ tests_dir }}

lint: format
{{ run_prefix }}ruff check --fix {{ code_dirs }}
# lint the code (including formatting)
lint *files=".":
{{ run }}ruff format {{ files }}
{{ run }}ruff check --fix {{ files }}

test:
{{ run_prefix }}pytest -x --ff {{ tests_dir }}
# run the tests
test *args:
{{ run }}pytest {{ args }}
53 changes: 28 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ and delegate to an executor:
- `flush`
- `isatty`
- `read`
- `readall`
- `read1`
- `readall`
- `readinto`
- `readline`
- `readlines`
Expand All @@ -91,36 +91,39 @@ In case of failure, one of the usual exceptions will be raised.
The `aiofiles.os` module contains executor-enabled coroutine versions of
several useful `os` functions that deal with files:

- `stat`
- `statvfs`
- `access`
- `getcwd`
- `link`
- `listdir`
- `makedirs`
- `mkdir`
- `path`:
- `path.abspath`
- `path.exists`
- `path.getatime`
- `path.getctime`
- `path.getmtime`
- `path.getsize`
- `path.isdir`
- `path.isfile`
- `path.islink`
- `path.ismount`
- `path.samefile`
- `path.sameopenfile`
- `readlink`
- `remove`
- `removedirs`
- `sendfile`
- `rename`
- `renames`
- `replace`
- `remove`
- `unlink`
- `mkdir`
- `makedirs`
- `rmdir`
- `removedirs`
- `link`
- `symlink`
- `readlink`
- `listdir`
- `scandir`
- `access`
- `getcwd`
- `path.abspath`
- `path.exists`
- `path.isfile`
- `path.isdir`
- `path.islink`
- `path.ismount`
- `path.getsize`
- `path.getatime`
- `path.getctime`
- `path.samefile`
- `path.sameopenfile`
- `sendfile`
- `stat`
- `statvfs`
- `symlink`
- `unlink`

### Tempfile

Expand Down
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ source = [

[tool.pytest.ini_options]
minversion = "8.3"
addopts = [
"-x",
"--ff",
]
testpaths = ["tests"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"

Expand Down Expand Up @@ -112,6 +117,7 @@ select = [
"PTH", # flake8-use-pathlib (PTH)
"PYI", # flake8-pyi (PYI)
"Q", # flake8-quotes (Q)
"RUF", # Ruff-specific rules (RUF)
"RET", # flake8-return (RET)
"RSE", # flake8-raise (RSE)
"S", # flake8-bandit (S)
Expand All @@ -131,7 +137,8 @@ ignore = [
]
fixable = [
"COM",
"I"
"I",
"RUF022", # https://docs.astral.sh/ruff/rules/unsorted-dunder-all/
]

[tool.ruff.lint.per-file-ignores]
Expand Down
8 changes: 4 additions & 4 deletions src/aiofiles/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@

__all__ = [
"open",
"tempfile",
"stdin",
"stdout",
"stderr",
"stderr_bytes",
"stdin",
"stdin_bytes",
"stdout",
"stdout_bytes",
"stderr_bytes",
"tempfile",
]
29 changes: 19 additions & 10 deletions src/aiofiles/base.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
from asyncio import get_running_loop
from collections.abc import Awaitable
from asyncio import get_running_loop, to_thread
from collections.abc import Awaitable, Callable, Coroutine
from contextlib import AbstractAsyncContextManager
from functools import partial, wraps
from functools import wraps


def wrap(func):
def wrap(func: Callable) -> Callable:
"""Converts the routine `func` into a coroutine.

The returned coroutine function runs the decorated function
in a separate thread.

Args:
func: A routine (regular function).

Returns:
A coroutine function.
"""

@wraps(func)
async def run(*args, loop=None, executor=None, **kwargs):
if loop is None:
loop = get_running_loop()
pfunc = partial(func, *args, **kwargs)
return await loop.run_in_executor(executor, pfunc)
async def _wrapper(*args, **kwargs) -> Coroutine:
return await to_thread(func, *args, **kwargs)

return run
return _wrapper


class AsyncBase:
Expand Down
23 changes: 11 additions & 12 deletions src/aiofiles/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,23 @@
from .base import wrap

__all__ = [
"access",
"getcwd",
"listdir",
"makedirs",
"mkdir",
"path",
"stat",
"readlink",
"remove",
"removedirs",
"rename",
"renames",
"replace",
"remove",
"unlink",
"mkdir",
"makedirs",
"rmdir",
"removedirs",
"symlink",
"readlink",
"listdir",
"scandir",
"access",
"wrap",
"getcwd",
"stat",
"symlink",
"unlink",
]

access = wrap(os.access)
Expand Down
2 changes: 1 addition & 1 deletion src/aiofiles/ospath.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

__all__ = [
"abspath",
"exists",
"getatime",
"getctime",
"getmtime",
"getsize",
"exists",
"isdir",
"isfile",
"islink",
Expand Down
2 changes: 1 addition & 1 deletion src/aiofiles/tempfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@

__all__ = [
"NamedTemporaryFile",
"TemporaryFile",
"SpooledTemporaryFile",
"TemporaryDirectory",
"TemporaryFile",
]


Expand Down
6 changes: 3 additions & 3 deletions src/aiofiles/threadpool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@

__all__ = (
"open",
"stdin",
"stdout",
"stderr",
"stderr_bytes",
"stdin",
"stdin_bytes",
"stdout",
"stdout_bytes",
"stderr_bytes",
)


Expand Down
2 changes: 1 addition & 1 deletion tests/threadpool/test_open.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async def test_file_async_context_aexit():

async def test_filetask_async_context_aexit():
async def _process_test_file(file_ctx, sleep_time: float = 1.0):
nonlocal file_ref
nonlocal file_ref # type: ignore
async with file_ctx as fp:
file_ref = file_ctx._obj
await asyncio.sleep(sleep_time)
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
requires =
tox>=4.26
min_version = 4.26
env_list = py39, py31{0,1,2,3,4}, pypy39, lint
env_list = py39, py31{0,1,2,3,4}, pypy39, check
no_package = false

[testenv:lint]
[testenv:check]
skip_install = true
basepython = python3.13
allowlist_externals = just
Expand Down
Loading