Skip to main content

Real async file I/O for Python, powered by io_uring

Project description

urio

PyPI Documentation

Async file I/O for Python that is actually asynchronous.

On Linux, urio submits reads and writes through io_uring and resolves completions on the event loop, with no thread parked per operation. On Windows it can do the same through overlapped I/O and an I/O completion port (opt-in, see below). Everywhere else — macOS, older kernels, sandboxes that block the syscalls — it falls back to a thread pool behind the same API, so one code path works on every platform.

The API follows aiofiles, which pioneered ergonomic async file access for asyncio by running the blocking syscalls in a thread pool — the only portable option at the time, and still the right fallback. urio keeps that familiar interface (which in turn mirrors the builtin open()), so switching is mostly changing the import.

What you get

  • Real async reads and writes: io_uring on Linux, IOCP on Windows.
  • The familiar file API: read, readline, readlines, write, writelines, seek, tell, truncate, flush, async iteration, text and binary modes.
  • An async pathlib.Path: urio.Path with async stat, mkdir, unlink, rename, symlink_to, read_text/write_text/read_bytes/write_bytes, iterdir, glob, walk, and the full set of pure-path helpers.
  • A thread-pool fallback wherever native async I/O is unavailable, same API.
  • Runs GIL-free on free-threaded CPython (3.14t and later).

Requirements

  • Python 3.12+, any OS.
  • Linux 5.6+ for the io_uring backend.
  • Windows 10/11 for the IOCP backend (opt-in via URIO_WINDOWS_IOCP=1; Windows otherwise uses the thread backend).

Anything else — macOS, older kernels, locked-down sandboxes — runs the thread backend automatically.

Installation

pip install urio

Prebuilt abi3 wheels (one per platform covers every CPython ≥ 3.12):

Platform Arch Backend
Linux (manylinux + musllinux) x86_64, aarch64, armv7 io_uring
Windows x64, x86 thread pool by default, IOCP opt-in
macOS x86_64, arm64 thread pool

Building from source needs a Rust toolchain and maturin; the crate compiles on every OS:

pip install -e ".[dev]"
maturin develop

Usage

import asyncio
import urio


async def main():
    # Files — binary and text modes, just like the builtin open().
    async with urio.open('greeting.txt', 'w') as f:
        await f.write('hello\nworld\n')

    async with urio.open('greeting.txt') as f:
        async for line in f:
            print(line.rstrip())

    # Async pathlib.
    p = urio.Path('greeting.txt')
    print(await p.exists())
    print((await p.stat()).st_size)
    print(await p.read_text())


asyncio.run(main())

Async pathlib

urio.Path is an async pathlib.Path. Pure-path operations (/ joining, name, suffix, parent, with_suffix, …) are ordinary synchronous properties; everything that touches the filesystem is async and goes through the active backend:

base = urio.Path('/tmp/project')
await base.mkdir(parents=True, exist_ok=True)

cfg = base / 'config.toml'
await cfg.write_text('name = "urio"\n')
print(await cfg.read_text())
print(await cfg.exists(), await cfg.is_file(), (await cfg.stat()).st_size)

async for child in base.iterdir():
    print(child.name)

On Linux, stat/mkdir/unlink/rename/symlink_to and the read/write helpers are genuine io_uring submissions; directory listing and metadata tweaks use the thread pool.

Choosing a backend

urio picks the fastest available backend at runtime. You can force one:

urio.set_backend('auto')     # default
urio.set_backend('thread')   # force the thread pool
urio.set_backend('uring')    # require io_uring (raises if unavailable)
urio.set_backend('iocp')     # require IOCP (Windows, needs URIO_WINDOWS_IOCP=1)

or via the environment: URIO_BACKEND=thread python app.py. On Windows, setting URIO_WINDOWS_IOCP=1 is enough on its own — auto-detection then selects IOCP.

How it works

On Linux, a single ring is created per event loop. Its io_uring instance is registered with an eventfd, and that fd is handed to loop.add_reader, so the loop wakes whenever completions are ready. Each submission maps a user_data id to an asyncio.Future; on wake-up the driver drains the eventfd, reaps every completion, and resolves the matching futures. Everything runs on the loop thread — no executor, no extra threads.

On Windows with IOCP enabled, files are opened for overlapped I/O and associated with the completion port that asyncio's ProactorEventLoop already owns, so completions are again reaped on the loop thread. Same shape, native Windows machinery.

Whatever the kernel touches is kept alive until the completion is reaped: reads land in a buffer the kernel fills directly, and writes submit against the caller's immutable Python bytes object (held by reference), so neither direction copies. This also side-steps the hardest io_uring footgun: the kernel using a buffer that Python has freed or moved.

Benchmarks

benchmarks/bench_vs_aiofiles.py runs the full cartesian product of {buffered write, write+fsync, read} × {binary, text} × {2000×4 KB, 500×64 KB, 100×1 MB, 20×8 MB}, plus a streaming read of a 128 MB file. It tries hard to be fair: the cache is warmed for every contender, each contender writes to its own files (so nobody inherits another's writeback pressure), and it reports the median of N runs with the [min–max] range. The Linux charts below are medians on CPython 3.14.6, measured on a 4-core Intel i5-6500, 32 GB RAM, ext4 on a SATA HDD (the Windows numbers are from a separate 8-core box; full specs in the benchmarks doc). Treat them as indicative and run it on your hardware. Each bar is one operation × mode × size; colour is the file size, and a bar past the 1.0× line means urio beat aiofiles.

Linux, io_uring. Buffered writes win across sizes: submission batching for small files (one io_uring_enter per loop tick instead of one syscall per op), zero-copy submission for large ones. Reads win small/medium and hold up at large sizes too — reads are zero-copy (the kernel fills the returned bytes directly) and big reads fan out to io-wq workers so their copies run on multiple cores. Durable (fsync) writes converge on disk bandwidth and stay competitive:

Linux io_uring speedup vs aiofiles

Windows, IOCP. Real async I/O via overlapped I/O and a completion port, reaped on the event-loop thread, with zero-copy writes and reads. Buffered writes are at parity to winning, durable writes win, large text wins outright (1.3–1.9×). Binary reads still lose: a warm ReadFile completes synchronously, so the kernel copies inline on the loop thread. The full test suite and a concurrency/cancellation stress test pass on Windows CPython 3.14, both GIL and free-threaded builds:

Windows IOCP speedup vs aiofiles

Free-threaded Python (no-GIL). The native module declares gil_used = false, and urio has the same thread-pool mode aiofiles does — so on no-GIL builds you lose nothing: use the thread backend for warm reads (parity with aiofiles), and io_uring/IOCP for writes, fsync, and latency, where the batching wins persist. The chart isolates the native backends' warm-read edge as the now-unshackled thread pools catch up: on Linux the small-read edge narrows but stays a win at every size, while on Windows the IOCP large-text-read wins drop below parity without the GIL:

Free-threading GIL vs no-GIL — io_uring and IOCP

Summary vs aiofiles (medians, CPython 3.14):

  • Linux writes win at small/medium sizes through submission batching (2.9–5.3×); large binary writes ride zero-copy submission (1.5× at 1 MB, parity at 8 MB), large text trails.
  • Linux reads win small/medium (2.6–3.5×) and, with zero-copy reads plus the io-wq punt, hold parity at 1 MB and 0.7× at 8 MB; large text reads win (1.1–1.4×). Warm streaming is a clear win — binary 6.4 GB/s vs aiofiles's 3.3 (1.9×).
  • Durable (fsync) writes converge on disk bandwidth and are competitive on both io_uring and IOCP. On ZFS every fsync forces a ZIL/txg commit, so large durable writes get ~24–71× slower — but aiofiles pays the same tax (speedup ~1.0×) and urio's batched fsync still wins on many small files. See the benchmarks doc.
  • Windows IOCP: writes parity to winning, durable writes win (1.1–1.7×), large text wins (1.3–1.9×); binary reads lose (the warm ReadFile copy runs inline on the loop thread).
  • The thread backend tracks aiofiles on both reads and writes, on every OS and in both GIL states — same strategy, used wherever io_uring or IOCP is unavailable or not selected.

Run it yourself: python benchmarks/bench_vs_aiofiles.py --markdown --json out.json (medians + raw samples). Methodology and full tables are in the benchmarks docs; raw sample data lives in benchmarks/data/.

Documentation

The full documentation lives at urio.readthedocs.io. ARCHITECTURE.md is the code-reader's tour: the ring/eventfd bridge, the zero-copy rules, linked chains, and the backend stack. To build the site locally:

pip install -e ".[docs]"
mkdocs serve

Scope & design notes

  • Metadata ops without an io_uring opcode use the thread pool. io_uring covers the hot path — read, write, fsync/fdatasync, openat, close, statx, mkdirat, unlinkat, renameat, symlinkat, linkat — while a few rarer operations (directory listing, chmod, readlink, realpath) have no opcode and run on the thread pool even under the io_uring backend, behind the same async API.
  • Text decoding is incremental. Reading a whole text file is one round-trip plus a single bulk decode, and sized read(n) fetches the whole request in one round-trip. Line-by-line iteration decodes incrementally (128 KB per refill), so it trails a C TextIOWrapper on pure line-streaming throughput; read binary and bulk-decode if that is your bottleneck. On free-threaded builds the codec runs in the executor so it parallelises across cores. Text streams are seekable in byte offsets, like io.TextIOWrapper.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

urio-0.1.1.tar.gz (175.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

urio-0.1.1-cp314-cp314t-win_amd64.whl (177.9 kB view details)

Uploaded CPython 3.14tWindows x86-64

urio-0.1.1-cp314-cp314t-manylinux_2_28_x86_64.whl (279.9 kB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.28+ x86-64

urio-0.1.1-cp314-cp314t-macosx_11_0_arm64.whl (212.8 kB view details)

Uploaded CPython 3.14tmacOS 11.0+ ARM64

urio-0.1.1-cp312-abi3-win_amd64.whl (177.6 kB view details)

Uploaded CPython 3.12+Windows x86-64

urio-0.1.1-cp312-abi3-win32.whl (170.6 kB view details)

Uploaded CPython 3.12+Windows x86

urio-0.1.1-cp312-abi3-musllinux_1_2_x86_64.whl (503.1 kB view details)

Uploaded CPython 3.12+musllinux: musl 1.2+ x86-64

urio-0.1.1-cp312-abi3-musllinux_1_2_aarch64.whl (467.1 kB view details)

Uploaded CPython 3.12+musllinux: musl 1.2+ ARM64

urio-0.1.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (291.8 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ x86-64

urio-0.1.1-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (275.6 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ ARMv7l

urio-0.1.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (269.0 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ ARM64

urio-0.1.1-cp312-abi3-macosx_11_0_arm64.whl (211.8 kB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

urio-0.1.1-cp312-abi3-macosx_10_12_x86_64.whl (219.3 kB view details)

Uploaded CPython 3.12+macOS 10.12+ x86-64

File details

Details for the file urio-0.1.1.tar.gz.

File metadata

  • Download URL: urio-0.1.1.tar.gz
  • Upload date:
  • Size: 175.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urio-0.1.1.tar.gz
Algorithm Hash digest
SHA256 3438daada0a8268f7493a9c1fe3df73507b58c0976aee9b558d3aa5e37dcac39
MD5 0ed6ca47237b8d8b4896358dde5e1f0f
BLAKE2b-256 f369a0d65ad6d114c96f3a1d1ada17b0bc7ac649a3edf364f1f2ccb0aadc5355

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1.tar.gz:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp314-cp314t-win_amd64.whl.

File metadata

  • Download URL: urio-0.1.1-cp314-cp314t-win_amd64.whl
  • Upload date:
  • Size: 177.9 kB
  • Tags: CPython 3.14t, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urio-0.1.1-cp314-cp314t-win_amd64.whl
Algorithm Hash digest
SHA256 84eb919631bb89de55a3243e2bde8e10600ff4e93e393c0d22ee9ceef5d68a91
MD5 f75200c9182c383d29ec10dc1bd7ccb4
BLAKE2b-256 ecb1fbdca57477f1d14d72efafc072fc1b2040afcb8c869e04182fbb00d3344b

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp314-cp314t-win_amd64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp314-cp314t-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for urio-0.1.1-cp314-cp314t-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e4066688e236576ddc2cc7bf8a2e2220d1264230601681c494c9efd901b387cf
MD5 56e7453454d4fa068085c0ffc6e9579b
BLAKE2b-256 2dbf5bc097d63393450c968a6549aea927e496e55fb47bd94a741dc990185770

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp314-cp314t-manylinux_2_28_x86_64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp314-cp314t-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for urio-0.1.1-cp314-cp314t-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 23c8747a06c5c6f88eb16a158091cf80ae764aa0178b9f5f00f60199c40cb69a
MD5 35afe17de597a146e9f5ded906bf8704
BLAKE2b-256 22bc32c5a95e6741c192c4fd50125010c0b6deddce92e298e75db0cf5c49e6a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp314-cp314t-macosx_11_0_arm64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-win_amd64.whl.

File metadata

  • Download URL: urio-0.1.1-cp312-abi3-win_amd64.whl
  • Upload date:
  • Size: 177.6 kB
  • Tags: CPython 3.12+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urio-0.1.1-cp312-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 80359f68cf5eb0e3360a979cb87f00580d3a44f5393107969ea513bf7f552062
MD5 6120f452963b1666c402f5b970b32f91
BLAKE2b-256 5f5e2836248c9f688308a7814a30e3858211ffc5a02e7f79f44b88038ec52a04

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-win_amd64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-win32.whl.

File metadata

  • Download URL: urio-0.1.1-cp312-abi3-win32.whl
  • Upload date:
  • Size: 170.6 kB
  • Tags: CPython 3.12+, Windows x86
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urio-0.1.1-cp312-abi3-win32.whl
Algorithm Hash digest
SHA256 cfed6864e12d1943cacafbca5a4581fe8c9158c276135cd8fd42d6cb9b6e9958
MD5 13ed992eccd5f673f2582579305017d8
BLAKE2b-256 4ecc1dae6fe6d3f1bbecb12af90dac13628e1b403e14cd0aeca2ccfef20b66e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-win32.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-musllinux_1_2_x86_64.whl.

File metadata

  • Download URL: urio-0.1.1-cp312-abi3-musllinux_1_2_x86_64.whl
  • Upload date:
  • Size: 503.1 kB
  • Tags: CPython 3.12+, musllinux: musl 1.2+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urio-0.1.1-cp312-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1be6f1ec585c81a61dc05b975bc12b60cb7a1fac461e1ba36c7ff9ab82026242
MD5 cd040f160b0e31f9d3f7871082ed4820
BLAKE2b-256 69ee2a0b31b21e4cd05099b37cd3ed85cbcf54e579c764e60fa998bde26640b5

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-musllinux_1_2_x86_64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for urio-0.1.1-cp312-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 4da215ee78c9e24e412fe842316cfc59e93d3e5eaa9b33424c243c9dd292a1a2
MD5 f0c2430151a4db26511a1c30b00132c5
BLAKE2b-256 4379e2275e7e7239c64bcfd2c28cc4b9f0af01b6282c9ab03e6393381b594413

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-musllinux_1_2_aarch64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for urio-0.1.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 bfe920a814d9061c8a80764a3863b3ae983a21704f4d19c10bd81a8c56fc9c45
MD5 946eefe1c974e2a7f8eb863de10dcd7b
BLAKE2b-256 79f6f676be72ada83a04c52764ec422e9e49968376c57d71c2852bfc5fb07c67

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.

File metadata

File hashes

Hashes for urio-0.1.1-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 4dee925cef88039891f5ae830579826cdc4f243c75ec626a4487c016a3ecec93
MD5 26f1a6e11980be111cdd33be716b4b0a
BLAKE2b-256 ff63bedcea0a6975c3ffaad02c710db68855e0461d45d78aa460bee246e20d46

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for urio-0.1.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 cc4ab67b9629ee2794874f70eb218d58bd59b0af668bb150eddc682db804e0be
MD5 1fb10f8d64e6a06d8382b572c62c82c9
BLAKE2b-256 bc2030b2748790e7d970b8ce0d8ff1f40669c8a2c306204ec3cf6ef632bacc13

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: urio-0.1.1-cp312-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 211.8 kB
  • Tags: CPython 3.12+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urio-0.1.1-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4f1a7060bf3fb8cf5a04de84a0a4d6b31e22ee34f920b74d2b1105be02573c20
MD5 71a3006387e57c23c55f646468b552d1
BLAKE2b-256 37c0854a53564cb736a8067b80e4bd5275adf2ad7dca6bee087bb7dea8f63dd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urio-0.1.1-cp312-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for urio-0.1.1-cp312-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 b8f16e537a8cab8a7d8fff42a0a40fb7e5a3842d34d2aa925066ae59e8f65a27
MD5 0100a6feaa63c6489087f9204fc5ad7c
BLAKE2b-256 2c35eb4022440fa62c1b96ae574f9f2201462a261b06c2d366c39a9323421f8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.1-cp312-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on meitham/urio

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page