Skip to main content

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

Project description

urio

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: 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

ARCHITECTURE.md is the code-reader's tour: the ring/eventfd bridge, the zero-copy rules, linked chains, and the backend stack. The full site is built with MkDocs Material:

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.0.tar.gz (173.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.0-cp314-cp314t-win_amd64.whl (177.7 kB view details)

Uploaded CPython 3.14tWindows x86-64

urio-0.1.0-cp314-cp314t-manylinux_2_28_x86_64.whl (279.7 kB view details)

Uploaded CPython 3.14tmanylinux: glibc 2.28+ x86-64

urio-0.1.0-cp314-cp314t-macosx_11_0_arm64.whl (212.6 kB view details)

Uploaded CPython 3.14tmacOS 11.0+ ARM64

urio-0.1.0-cp312-abi3-win_amd64.whl (177.4 kB view details)

Uploaded CPython 3.12+Windows x86-64

urio-0.1.0-cp312-abi3-win32.whl (170.4 kB view details)

Uploaded CPython 3.12+Windows x86

urio-0.1.0-cp312-abi3-musllinux_1_2_x86_64.whl (502.8 kB view details)

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

urio-0.1.0-cp312-abi3-musllinux_1_2_aarch64.whl (466.9 kB view details)

Uploaded CPython 3.12+musllinux: musl 1.2+ ARM64

urio-0.1.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (291.6 kB view details)

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

urio-0.1.0-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl (275.4 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ ARMv7l

urio-0.1.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (268.8 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.17+ ARM64

urio-0.1.0-cp312-abi3-macosx_11_0_arm64.whl (211.6 kB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

urio-0.1.0-cp312-abi3-macosx_10_12_x86_64.whl (219.1 kB view details)

Uploaded CPython 3.12+macOS 10.12+ x86-64

File details

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

File metadata

  • Download URL: urio-0.1.0.tar.gz
  • Upload date:
  • Size: 173.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.0.tar.gz
Algorithm Hash digest
SHA256 69961835c7c892820412a5b397bf5be4fa4cf5c15cb2fd7e8c7f7d0d6aecf432
MD5 cf91255fb7f7c1385e71bb48a270bb51
BLAKE2b-256 707d358d812afbb0c056dba9f4b05e92f0b3ddccc00fd998b4cae92615fcd43c

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: urio-0.1.0-cp314-cp314t-win_amd64.whl
  • Upload date:
  • Size: 177.7 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.0-cp314-cp314t-win_amd64.whl
Algorithm Hash digest
SHA256 5a88abcd4b7bd6c6660a1fc58b83a8473a105172adabb89c82036343188142be
MD5 7f79ebb9f156ba938ccce91a33e83ddb
BLAKE2b-256 b8c2b8d81793e0311d7b7006f92f82052ff2190f77d5578a982c9d71d32a16f6

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for urio-0.1.0-cp314-cp314t-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d1fa089cbd1a3f35d6d8b3cd597ce30d525b48906d0e559bdccbe9375b675767
MD5 cca5321738541a6f844a538c0b69b3c4
BLAKE2b-256 30a02a3fa52bf24f48d3f906ed8a587f7d904d5afcac69dd49691aa2ac1c1ad1

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for urio-0.1.0-cp314-cp314t-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 515f86788452d1907196a7af009ee336c5a2bbacfd1751a5ab5913d666de50cd
MD5 2fc7fa090691135cc3b262de04135000
BLAKE2b-256 0aca45a7975ec247481f6261c8a82a15e5fd9fb22355633b64f902c14f6f715d

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: urio-0.1.0-cp312-abi3-win_amd64.whl
  • Upload date:
  • Size: 177.4 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.0-cp312-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 739e9beac4af971a7cf3b4f5e13aacedbd123c358565bcb37b1d290ac24076d5
MD5 83e674bb7b3c8981ce3cb4c36d6a51b7
BLAKE2b-256 fc3f3572dab5b2744e9f510c8b9cc40327520fd6df9e3a2d3507c8f5e36d2395

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: urio-0.1.0-cp312-abi3-win32.whl
  • Upload date:
  • Size: 170.4 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.0-cp312-abi3-win32.whl
Algorithm Hash digest
SHA256 5d90800c9dac960a5531bc1677e560f94a660bbd12c0d1cbecb06c2620e43fa8
MD5 ec5215a4cf155a54aa0d0f9c94bf942e
BLAKE2b-256 b71dd3b6df093ca345338f594847d608ec86dbefda2dc217a63932e72ca500ca

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: urio-0.1.0-cp312-abi3-musllinux_1_2_x86_64.whl
  • Upload date:
  • Size: 502.8 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.0-cp312-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 c6eb55062bdac62d5147ad75a8cf7d3d556de10ad17ed26b02a0bee4a2863c04
MD5 59c1632011979aa94275ae5fedbe8265
BLAKE2b-256 df9289ddc176bbb5972c70ee9a9a9d7ff25d54dc7a1cae93081647e3728bdaea

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for urio-0.1.0-cp312-abi3-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 c4890ce152264b3e377c8ef0c6d736faee8a04803108e9c74ee846111aa91da9
MD5 08b6e5f27cacab17d2da4461629803b1
BLAKE2b-256 204c97abda5414b1ee66488fcd173ae6c8e8da35da4aefb99fc931b2b1c62470

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for urio-0.1.0-cp312-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 98c9fa0d31882d63afe586c5e7fa3515df53827c1fd2b6d85fc7c1bfef428a8d
MD5 54fdbb5b9022390a0840a4644bf3fd79
BLAKE2b-256 9f811e121490477043efd4d001c71beec9ceb05f6642db7082b8159fd99ac516

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for urio-0.1.0-cp312-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm Hash digest
SHA256 cc2a4ce9c83be1b76ec217f086e4fdb5ccf408dc00271281cd83bea711a7da03
MD5 f7874ca7f5a3047046745b7d3c10e4f6
BLAKE2b-256 c4cc5af4349910a464a280bb28f350b7139868684f3057c728bab3bce20304eb

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for urio-0.1.0-cp312-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 efd1a6bc846698acd26be129c4f747bd9e8e6463696c0552c1fb5cd2e5ecc898
MD5 5dfa686255aa9d51d138831be2892e4b
BLAKE2b-256 fa079b54ca0e8796acaa49bf00db7efcac9a275171ee07a70e020d75b2c25985

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: urio-0.1.0-cp312-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 211.6 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.0-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1a36fd70bed7e637fba7fb5cf424c9f2934cd354740c403d516fc3fd652dadaa
MD5 b3436c5087c4c48d9c92f2ee06ec48f9
BLAKE2b-256 457d66bb12b5c01292aed53563250317a7b27a885e7b51a27875e53e8007778a

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for urio-0.1.0-cp312-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 b065312db57d58cd28577b24a059bfbb777cd8279438f0b1d006f0ffe4a768c1
MD5 e9e59802eb51ef3cd39e4d4b817f1a72
BLAKE2b-256 ae968b96bdd9098388e09a90df4c9e17f6dc07d86e1e18f698701de0a23b1f26

See more details on using hashes here.

Provenance

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