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.2.tar.gz (177.0 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.2-cp314-cp314t-win_amd64.whl (178.6 kB view details)

Uploaded CPython 3.14tWindows x86-64

urio-0.1.2-cp314-cp314t-manylinux_2_28_x86_64.whl (280.6 kB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.28+ x86-64

urio-0.1.2-cp314-cp314t-macosx_11_0_arm64.whl (213.5 kB view details)

Uploaded CPython 3.14tmacOS 11.0+ ARM64

urio-0.1.2-cp312-abi3-win_amd64.whl (178.3 kB view details)

Uploaded CPython 3.12+Windows x86-64

urio-0.1.2-cp312-abi3-win32.whl (171.3 kB view details)

Uploaded CPython 3.12+Windows x86

urio-0.1.2-cp312-abi3-musllinux_1_2_x86_64.whl (503.7 kB view details)

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

urio-0.1.2-cp312-abi3-musllinux_1_2_aarch64.whl (467.8 kB view details)

Uploaded CPython 3.12+musllinux: musl 1.2+ ARM64

urio-0.1.2-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (292.5 kB view details)

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

urio-0.1.2-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (276.3 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ ARMv7l

urio-0.1.2-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (269.7 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ ARM64

urio-0.1.2-cp312-abi3-macosx_11_0_arm64.whl (212.5 kB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

urio-0.1.2-cp312-abi3-macosx_10_12_x86_64.whl (220.0 kB view details)

Uploaded CPython 3.12+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: urio-0.1.2.tar.gz
  • Upload date:
  • Size: 177.0 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.2.tar.gz
Algorithm Hash digest
SHA256 63a2f2c72a11429cdd9469be2a6d2e123a8a7a027173152f50b29825d1065f0b
MD5 ed60b0c87700f7ce0a9cfe865a06cad3
BLAKE2b-256 04107b57c237ad6e2c7b551d96798720eedf32dad3996a9ed284893fa3832f9e

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2.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.2-cp314-cp314t-win_amd64.whl.

File metadata

  • Download URL: urio-0.1.2-cp314-cp314t-win_amd64.whl
  • Upload date:
  • Size: 178.6 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.2-cp314-cp314t-win_amd64.whl
Algorithm Hash digest
SHA256 32a93c5299acffa0d7cec7bfee57c86c9d4e5726e6ef515b78556991e6438ac9
MD5 20b97643d8f2152c0d97bf95108673aa
BLAKE2b-256 88ce6b53c0bc2786975fac73326836959222f361c705840f2f85442445581b12

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp314-cp314t-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for urio-0.1.2-cp314-cp314t-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 7bd9b55c568f09bc767d4ddfbb6ddda590d44288b0961029410621acc5e46276
MD5 1e5adece563b351d33f3817d3d0779eb
BLAKE2b-256 b4fd6c958b6286a8d97d6cb45d5e179ad7a9e924dfab89b3bdf072181840b57e

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp314-cp314t-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for urio-0.1.2-cp314-cp314t-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e86afe008a544c65dcdd3a144cce3b41b7a0493c90129a9c889d765c05dd00b0
MD5 9a40855f87f51ee275e14a3b03d47e85
BLAKE2b-256 85dd90231d1764ff9c4774d6c838f7860c6b5887be0e2f32a37b6f5906c1a049

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-win_amd64.whl.

File metadata

  • Download URL: urio-0.1.2-cp312-abi3-win_amd64.whl
  • Upload date:
  • Size: 178.3 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.2-cp312-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 ef1be45e4142f19584087742aba7ab27cff7b8b5b62ae3b701435bf9ab98a636
MD5 cd79fc291e8fff3253687fbbfb3e265a
BLAKE2b-256 a852f5be6ea251d83ac363f73e4a18f4fad597318383a6c5410c9995092788ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-win32.whl.

File metadata

  • Download URL: urio-0.1.2-cp312-abi3-win32.whl
  • Upload date:
  • Size: 171.3 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.2-cp312-abi3-win32.whl
Algorithm Hash digest
SHA256 2d639598d977c36241ed51c8bcbe1ae423728fc9f36739baf50b24553ad12740
MD5 50fae7d100814365a554b9e22536419f
BLAKE2b-256 87233c16b9a4f16bdf59380af2766ed7d1d07fe5da1b56ac323baf1f3ae240b7

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-musllinux_1_2_x86_64.whl.

File metadata

  • Download URL: urio-0.1.2-cp312-abi3-musllinux_1_2_x86_64.whl
  • Upload date:
  • Size: 503.7 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.2-cp312-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 f57453b78c18fe7b044bf93d24684a7f0e8d165558dada34eae347393fe92073
MD5 da483e4c9cc671ce90494244144ba07e
BLAKE2b-256 cfe5a0d9391a8715b2238d51ca10514c798b9682b48a11a3f872f5eb5c003b7c

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for urio-0.1.2-cp312-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 fd61c12490cdd50a1f2600549bc8c24d402ea3727b610081b1ee19553c7cdbd6
MD5 e5da1f8e80e75083f53eab8d03be68d4
BLAKE2b-256 82c749389480ebe295b8af3aeef3b3c971fd68221e9fc1cd334fe90c82830b69

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for urio-0.1.2-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9682cfe87eb0611956d02c057536e6985266c66a23a08b75a3065c04e02a8d68
MD5 48e3d6e616bedab59fd4eff4b8ae9d01
BLAKE2b-256 12276dd139b8ca272b815a9162f6e1b4ea0fb59cbb7de747759b65f7983c959d

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl.

File metadata

File hashes

Hashes for urio-0.1.2-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 1e49522cb347b03d78a000bff8ff52cfb1b7e1028cc5568bd39b587913bca2e5
MD5 d8b7b2df48e18e7592b33ac5a92fce6c
BLAKE2b-256 16b966442975b0b43ca386de1114f995469bd284100b63061e1a891a287fa297

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for urio-0.1.2-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 e6176b3875641e5839965c12278b2ec69b83b9c3c51bf7e670433ec252a44106
MD5 2b927978b42f8b08e641a2c9e03b9827
BLAKE2b-256 84466a6bb8c5067b6b2b0c3b4549909a613bc0f5e45cd8c44e2cc0ce48a2672a

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: urio-0.1.2-cp312-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 212.5 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.2-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 81fd9af953f1a99ee0d1622203784131f82ecb53a512e688e7363b06dbd63c73
MD5 0bda7d1cd727fe7449ff127cdabea918
BLAKE2b-256 73453d95b717cbd75458f3cdb499d452603a30e652645ac4415528dcde5667e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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.2-cp312-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for urio-0.1.2-cp312-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 00adc41d7f6548af750b314b710fe23fc94dcbe8ec6ed141d30b987570af49ce
MD5 66527bfb3cf65dc00dac7a65a6a48b18
BLAKE2b-256 22f892f5f0cceff9ee66a206300c3d5bee93e3f0f9dd1d01821a2ea75455b2e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for urio-0.1.2-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