aiofile Documentation

repository·master·Indexed 20 days ago

https://github.com/mosquito/aiofile

aiofile provides real asynchronous file operations with asyncio support, utilizing high-performance backends like Linux io_uring and libaio via the caio library. It offers a high-level async_open API similar to Python's built-in open(), a low-level AIOFile API for offset-based I/O, and specialized wrappers such as TextFileWrapper, BinaryFileWrapper, and LineReader for efficient sequential and line-by-line reading.

Tokens
6.8K
Snippets
23
Records
28
Agent score
69%

What's inside aiofile

  1. How to open special files (procfs, sysfs, etc.)

    master

    Linux native AIO and io_uring backends do not support special files like /proc/, /sys/, or Unix pipes. To work with these, you must provide a compatible context object using caio.thread_aio_asyncio.AsyncioContext. This switches the implementation to a thread-based approach which is compatible with special filesystems.

    Note: The custom context object should be reused if opening multiple special files.

    import asyncio
    from aiofile import async_open
    from caio import thread_aio_asyncio
    from contextlib import AsyncExitStack
    
    async def main():
        async with AsyncExitStack() as stack:
            # Create and reuse a thread-based context for special files
            ctx = await stack.enter_async_context(
                thread_aio_asyncio.AsyncioContext()
            )
    
            # Open special file with custom context
            src = await stack.enter_async_context(
                async_open("/proc/cpuinfo", "r", context=ctx)
            )
    
            # Open regular file with default context
            dest = await stack.enter_async_context(
                async_open("/tmp/cpuinfo", "w")
            )
    
            async for line in src:
                await dest.write(line)
    
    asyncio.run(main())
  2. Troubleshoot caio Linux backend issues

    master

    If you encounter issues with caio Linux backends (io_uring or libaio), they are typically environment-specific rather than bugs. You can resolve these by upgrading your kernel, using a compatible filesystem, or switching to a different backend.

    To switch backends, you can use one of the following methods:

    import asyncio
    from aiofile import async_open
    from caio import linux_aio_asyncio, thread_aio_asyncio
    
    async def main():
        # Create specific contexts
        linux_ctx = linux_aio_asyncio.AsyncioContext()
        threads_ctx = thread_aio_asyncio.AsyncioContext()
    
        # Use the linux context for writing
        async with async_open("/tmp/test.txt", "w", context=linux_ctx) as afp:
            await afp.write("Hello")
    
        # Use the thread context for reading
        async with async_open("/tmp/test.txt", "r", context=threads_ctx) as afp:
            print(await afp.read())
    
    asyncio.run(main())
  3. Read or write files linearly with `Reader` and `Writer`

    master

    Since AIOFile has no internal pointer, use the Reader and Writer helpers to perform sequential (linear) I/O operations.

    • Writer(afp): Wraps an AIOFile to provide sequential writing.
    • Reader(afp, chunk_size=...): Wraps an AIOFile to provide sequential reading in chunks.
    import asyncio
    from aiofile import AIOFile, Reader, Writer
    
    async def main():
        async with AIOFile("/tmp/hello.txt", 'w+') as afp:
            writer = Writer(afp)
            reader = Reader(afp, chunk_size=8)
    
            await writer("Hello")
            await writer(" ")
            await writer("World")
            await afp.fsync()
    
            async for chunk in reader:
                print(chunk)
    
    asyncio.run(main())
  4. Manually manage caio contexts with async_open

    master

    To ensure a specific backend is used for a file operation, you can manually create a context from the caio package and pass it to aiofile.async_open using the context parameter. This bypasss the default implementation selection.

    from aiofile import async_open
    from caio import linux_aio_asyncio, thread_aio_asyncio
    
    # Example: Using a specific Linux AIO context
    linux_ctx = linux_aio_asyncio.AsyncioContext()
    async with async_open("filename.txt", "r", context=linux_ctx) as afp:
        content = await afp.read()
    
    # Example: Using a thread-based context
    threads_ctx = thread_aio_asyncio.AsyncioContext()
    async with async_open("filename.txt", "r", context=threads_ctx) as afp:
        content = await afp.read()
  5. Use the `clone` helper for concurrent I/O

    master

    The clone helper allows you to create a second file-like object from a single existing descriptor. This second object has its own independent file pointer (offset), enabling multiple concurrent asynchronous operations on the same file without opening the file multiple times.

    Warning: This optimization may perform poorly on Windows and might not be worth the complexity on that platform.

    import asyncio
    import hashlib
    import sys
    import aiofile
    
    async def hasher(name, hash_func, afp):
        loop = asyncio.get_running_loop()
        async for chunk in afp.iter_chunked(2 ** 20):
            await loop.run_in_executor(None, hash_func.update, chunk)
        print(name, hash_func.hexdigest())
    
    async def main():
        async with aiofile.async_open(sys.argv[1], "rb") as source:
            hashers = [
                ("MD5", hashlib.md5()),
                ("SHA1", hashlib.sha1()),
                ("SHA256", hashlib.sha256()),
                ("SHA512", hashlib.sha512()),
            ]
    
            # Use clone to give each hasher its own offset on the same file
            await asyncio.gather(*[
                hasher(name, hash_func, await aiofile.clone(source))
                for name, hash_func in hashers
            ])
    
    asyncio.run(main())
  6. Use the `async_open` high-level API

    master

    The async_open helper provides a file-like interface similar to Python's built-in open(), making it easy to integrate asynchronous file I/O into existing workflows. It returns an object that supports standard asynchronous methods for reading, writing, and seeking.

    Supported methods:

    • async def read(length=-1): Reads a chunk from the file. -1 reads to the end.
    • async def write(data): Writes a chunk to the file.
    • def seek(offset): Sets the file pointer position.
    • def tell(): Returns the current file pointer position.
    • async def readline(size=-1, newline="\n"): Reads until a newline or EOF. (Note: For high-performance line-by-line reading, prefer the __aiter__ iterator or LineReader).
    • def __aiter__() -> LineReader: An asynchronous iterator over lines.
    • def iter_chunked(chunk_size: int = 32768) -> Reader: An asynchronous iterator over chunks.
    • .file: Accesses the underlying AIOFile object.
    import asyncio
    from pathlib import Path
    from aiofile import async_open
    
    tmp_filename = Path("/tmp/hello.txt")
    
    async def main():
        async with async_open(tmp_filename, 'w+') as afp:
            await afp.write("Hello ")
            await afp.write("world")
            afp.seek(0)
    
            print(await afp.read())
    
    asyncio.run(main())
  7. Use the low-level `AIOFile` API

    master

    The AIOFile class is a low-level interface where operations do not rely on an internal file pointer. Instead, you must pass an offset (in bytes) to read and write methods. This allows for highly efficient, independent I/O operations at specific locations in the file.

    Key Methods:

    • await afp.read(length, offset=0)
    • await afp.write(data, offset=0)
    • await afp.fsync()
    import asyncio
    from aiofile import AIOFile
    
    async def main():
        async with AIOFile("hello.txt", 'w+') as afp:
            payload = "Hello world\n"
    
            # Perform multiple writes at different offsets concurrently
            await asyncio.gather(
                *[afp.write(payload, offset=i * len(payload)) for i in range(10)]
            )
    
            await afp.fsync()
            assert await afp.read(len(payload) * 10) == payload * 10
    
    asyncio.run(main())
  8. Read files line by line with `LineReader`

    master

    For efficient line-by-line reading, use the LineReader helper. It maintains an internal buffer and reads file fragments in chunks (default 4KB) to search for line boundaries. This is more performant than using async_open().readline() for small lines because it reuses the read buffer.

    import asyncio
    from aiofile import AIOFile, LineReader, Writer
    
    async def main():
        async with AIOFile("/tmp/hello.txt", 'w+') as afp:
            writer = Writer(afp)
            await writer("Hello\n")
            await writer("World\n")
            await writer("From async world")
            await afp.fsync()
    
            async for line in LineReader(afp):
                print(line)
    
    asyncio.run(main())
  9. Select a caio backend via CAIO_IMPL

    master

    You can control which backend caio uses by setting the CAIO_IMPL environment variable at runtime. This is useful for switching between high-performance Linux backends and more compatible thread-based fallbacks without changing code.

    # Available CAIO_IMPL values:
    # uring  -> Linux io_uring (requires kernel ≥ 5.1)
    # linux  -> Linux libaio
    # thread -> C-based thread pool implementation
    # python -> pure-Python thread-based fallback
  10. Use AIOFile for asynchronous file I/O

    master

    The AIOFile class provides an asynchronous interface for file operations, allowing you to read from and write to files without blocking the event loop. It supports both text and binary modes and can be used as an asynchronous context manager.

    Key features include:

    • Asynchronous Context Manager: Use async with AIOFile(...) as f: to ensure the file is automatically opened and closed.
    • Text and Binary Modes: Specify modes like 'r', 'w', 'a', 'x', or 'rb', 'wb' similar to Python's built-in open().
    • Offset-based I/O: Methods like read and write support an offset parameter for positional access.
    • Cloning: Use clone() to increment a reference count, deferring the actual file closure until all clones are closed.
    import asyncio
    from aiofile.aio import AIOFile
    
    async def main():
        # Text mode usage
        async with AIOFile('example.txt', mode='w', encoding='utf-8') as f:
            await f.write('Hello, AIOFile!')
    
        async with AIOFile('example.txt', mode='r') as f:
            content = await f.read()
            print(content)
    
        # Binary mode usage
        async with AIOFile('data.bin', mode='wb') as f:
            await f.write_bytes(b'\x00\x01\x02')
    
    asyncio.run(main())
  11. Open files with async_open()

    master

    Use async_open() to obtain a high-level file wrapper that supports both binary and text modes. If the mode is not binary, it returns a TextFileWrapper; otherwise, it returns a BinaryFileWrapper. The returned wrapper can be used as an asynchronous context manager.

    Arguments:

    • file_specifier: A str, Path, or FileIOType object.
    • mode: The file mode (e.g., 'r', 'w', 'rb', 'wb'). Defaults to 'r'.
    • *args, **kwargs: Passed to the underlying AIOFile constructor.
    from aiofile.utils import async_open
    
    # For text mode
    async with async_open("example.txt", mode="r") as f:
        content = await f.read()
        print(content)
    
    # For binary mode
    async with async_open("example.bin", mode="rb") as f:
        data = await f.read(1024)