# aiofiles: async file IO for asyncio without blocking the event loop

> aiofiles wraps ordinary blocking file objects in a thread pool so asyncio code can await reads, writes and os calls. It is a thin adapter, not a new IO engine, and the thread hop is the whole mechanism.

**Tinche/aiofiles** — File support for asyncio

- Repository: https://github.com/Tinche/aiofiles
- Stars: 3,264 · Forks: 174
- Language: Python
- License: Apache-2.0
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/tinche-aiofiles

## The blocking call hiding inside an asyncio service

Local disk IO in Python is blocking. The README states the problem plainly: ordinary local file IO cannot easily and portably be made asynchronous, so doing it can interfere with asyncio applications that should not block the executing thread. A single await-less open() plus read() on a large file or a slow mount holds the event loop, and every other task on that loop waits. That is the failure aiofiles targets, and it is a real one for services that log to disk, tail files, or write uploads between HTTP responses.

The intended audience is asyncio application authors who want to keep the familiar file API. The project describes itself as an Apache2 licensed library written in Python for handling local disk files in asyncio applications. It is not a database driver, not a network client, and not a replacement for aiohttp or asyncpg. Its scope is the local filesystem and a handful of os functions.

## How aiofiles delegates to a thread pool

The mechanism is delegation, not kernel-level async IO. According to the README, aiofiles introduces asynchronous versions of files that support delegating operations to a separate thread pool. aiofiles.open() mirrors the builtin open and additionally accepts optional loop and executor arguments. If loop is absent the default loop is used per the set asyncio policy; if executor is not specified, the default event loop executor is used.

The returned object has an API identical to an ordinary file, except a listed set of methods become coroutines that delegate to an executor: close, flush, isatty, read, readall, read1, readinto, readline, readlines, seek, seekable, tell, truncate, writable, write and writelines. Everything else stays synchronous. That split matters: attribute access, iteration setup and the object's own bookkeeping do not hop threads, only the listed calls do.

aiofiles.os goes further and offers executor-enabled coroutine versions of os functions that deal with files, including stat, statvfs, sendfile, rename, renames, replace, remove, unlink, mkdir, makedirs, rmdir, removedirs, link, symlink, readlink, listdir, scandir, access and getcwd, plus path helpers such as path.exists, path.isfile, path.isdir, path.getsize and path.sameopenfile. The tempfile interface covers TemporaryFile, NamedTemporaryFile, SpooledTemporaryFile and TemporaryDirectory, with results wrapped in a context manager so async with and async for work.

Because each call is a thread hop, the cost profile is different from a synchronous read. Small reads in a tight loop pay scheduling overhead per call; one large read pays it once. The README does not publish benchmark numbers, so treat that as a design consequence of the thread pool model rather than a measured claim.

## Installing aiofiles and a first real read

The README gives one installation command. aiofiles has no runtime dependencies according to pyproject.toml, and requires Python 3.9 or newer.

```bash
pip install aiofiles
```

The first real use is opening a file and awaiting the read. The README shows this exact pattern, with the file contents printed afterwards.

```python
async with aiofiles.open('filename', mode='r') as f:
    contents = await f.read()
print(contents)
```

Line-by-line reading uses asynchronous iteration instead of a while loop. The README shows the default mode being used here, so the file is opened in text mode.

```python
async with aiofiles.open('filename') as f:
    async for line in f:
        ...
```

If you need a scratch file rather than a named one, the tempfile interface is the point. This example comes from the README and writes bytes, so the mode is binary.

```python
async with aiofiles.tempfile.TemporaryFile('wb') as f:
    await f.write(b'Hello, World!')
```

One thing to get right early: aiofiles.open is a coroutine, not a context manager on its own. You cannot write `with aiofiles.open(...)` and expect it to work; the README's examples all use async with. The same applies to the tempfile wrappers, which the README says return results wrapped with a context manager for use with async with and async for.

## Where aiofiles is the wrong tool

The thread pool is the limitation. Moving a blocking call to an executor frees the event loop, but it does not make the underlying operation cancellable in the way a genuinely asynchronous syscall would be. If a task awaiting f.read() is cancelled, the executor thread is already inside the blocking read; the documentation does not describe a mechanism for interrupting it. For long reads on slow or network-mounted filesystems, that gap is the thing to think about before adopting.

Throughput is bounded by the executor. Every listed method consumes a thread while it runs, and the default executor has a finite worker count. A service that fires thousands of concurrent small reads will queue on that pool rather than on the disk, and raising the worker count trades one bottleneck for another. The README does not document tuning guidance for this, so sizing is left to the caller.

It is also the wrong tool for remote storage. aiofiles handles local disk files; the README says so directly. If your data lives behind S3, an HTTP API or a database, you want a client for that protocol, not a thread-pool wrapper around open(). And if you need io_uring or POSIX AIO semantics, the thread pool model is a different design entirely.

## aiofiles versus aiofile and anyio

The closest named alternative in the search data is aiofile. The difference is the layer at which asynchrony is achieved: aiofiles keeps the standard blocking file object and moves each call to a thread pool executor, whereas aiofile targets the operating system's asynchronous IO facilities directly. That gives aiofile a different portability and platform story, and a different cancellation story, at the cost of not being a drop-in for every builtin open() behaviour. If your workload is dominated by large sequential reads and you are on Linux, the lower-level approach is worth evaluating; if you want the builtin API and CPython plus PyPy support, aiofiles is the smaller change.

anyio is a different kind of comparison, because it is a general async compatibility layer rather than a file library. anyio offers its own file IO primitives, and projects already built on anyio may prefer to stay inside its API rather than add a second abstraction. aiofiles has no runtime dependencies per pyproject.toml, so adding it to an anyio project is not a dependency-tree problem, but it is still a second way of doing the same job in one codebase.

## Versioning, licence and the cost of upgrading

Releases are not frequent. v23.2.1 landed on 2023-08-11, v24.1.0 on 2024-06-24, and v25.1.0 on 2025-10-09. The version numbers are calendar-based, so a jump from 24.1.0 to 25.1.0 is a year boundary rather than a semver signal about breaking changes; read CHANGELOG.md before upgrading rather than inferring from the number. The last push to main was on 2026-08-29.

Supported interpreters are listed in pyproject.toml as CPython 3.9 through 3.14 and PyPy, with the classifier Development Status :: 5 - Production/Stable. There are no runtime dependencies, which keeps the upgrade surface small: a version bump changes aiofiles itself and nothing else in your lockfile.

The licence is Apache-2.0, declared both in pyproject.toml and in the README. Apache-2.0 includes an explicit patent grant and requires that you preserve the NOTICE file, which is present at the repository root. That is a factual difference from MIT, which has no such clause. It is not legal advice; if your organisation has a policy on NOTICE propagation, check it against the file in the repository.

## Conclusion

Adopt aiofiles when your asyncio service does local file IO on the same thread as the event loop and you want the standard open() API with await in front of it. Skip it when your storage is already async (an HTTP API, a database driver, or a library such as aiofile that uses io_uring or thread-based AIO at a lower level), or when you need cancellation of an in-flight read, which the executor delegation does not give you. Before committing, check that the methods you call (readinto, sendfile, scandir) are in the documented coroutine list, confirm Python 3.9 or newer, and pin the version you install.

## FAQ

### What is aiofiles in Python?

It is an Apache2 licensed library for handling local disk files in asyncio applications, providing asynchronous versions of files that delegate operations to a separate thread pool. Its API mirrors the builtin open(), with a listed set of methods turned into coroutines.

### How to install aiofiles?

The README gives a single command: pip install aiofiles. The package requires Python 3.9 or newer and has no runtime dependencies.

### How to use aiofiles to read a file?

Open with the aiofiles.open coroutine inside an async with block and await the read, for example: async with aiofiles.open('filename', mode='r') as f: contents = await f.read(). Asynchronous iteration over lines is also supported.

### What does aiofiles do compared with the builtin open?

The returned object has an API identical to an ordinary file, except that methods such as read, write, seek, tell and close are coroutines that delegate to an executor. The README also documents loop and executor arguments that builtin open does not accept.

## Sources

- [Issues](https://github.com/Tinche/aiofiles/issues)
- [License: Apache-2.0](https://github.com/Tinche/aiofiles/blob/main/LICENSE)
- [README](https://github.com/Tinche/aiofiles/blob/main/README.md)
- [Releases](https://github.com/Tinche/aiofiles/releases)
- [Tinche/aiofiles on GitHub](https://github.com/Tinche/aiofiles)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tinche-aiofiles
