././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1483567275.0
aiofile-3.9.0/LICENCE.md 0000644 0000000 0000000 00000024377 13033270253 011460 0 ustar 00 Apache License
==============
_Version 2.0, January 2004_
_<>_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1728383963.7079215
aiofile-3.9.0/README.md 0000644 0000000 0000000 00000030435 14701205734 011350 0 ustar 00 # AIOFile
[](https://github.com/mosquito/aiofile/actions?query=branch%3Amaster) [](https://pypi.python.org/pypi/aiofile/) [](https://pypi.python.org/pypi/aiofile/) [](https://pypi.python.org/pypi/aiofile/) [](https://pypi.python.org/pypi/aiofile/) [](https://coveralls.io/github/mosquito/aiofile?branch=master)
Real asynchronous file operations with asyncio support.
## Status
Development - Stable
## Features
* Since version 2.0.0 using [caio](https://pypi.org/project/caio), which contains linux `libaio` and two
thread-based implementations (c-based and pure-python).
* AIOFile has no internal pointer. You should pass `offset` and
`chunk_size` for each operation or use helpers (Reader or Writer).
The simplest way is to use `async_open` for creating object with
file-like interface.
* For Linux using implementation based on [libaio](https://pagure.io/libaio).
* For POSIX (MacOS X and optional Linux) using implementation
based on [threadpool](https://github.com/mbrossard/threadpool/).
* Otherwise using pure-python thread-based implementation.
* Implementation chooses automatically depending on system compatibility.
## Limitations
* Linux native AIO implementation is not able to open special files.
Asynchronous operations against special fs like `/proc/` `/sys/` are not
supported by the kernel. It's not a aiofile's or caio issue.
In these cases, you might switch to thread-based implementations
(see [Troubleshooting](#troubleshooting) section).
However, when used on supported file systems, the linux implementation has a
smaller overhead and is preferred but it's not a silver bullet.
## Code examples
All code examples require Python 3.6+.
### High-level API
#### `async_open` helper
Helper mimics python file-like objects, it returns file-like
objects with similar but async methods.
Supported methods:
* `async def read(length = -1)` - reading chunk from file, when length is
`-1`, will be reading file to the end.
* `async def write(data)` - writing chunk to file
* `def seek(offset)` - setting file pointer position
* `def tell()` - returns current file pointer position
* `async def readline(size=-1, newline="\n")` - read chunks until
newline or EOF. Since version 3.7.0 `__aiter__` returns `LineReader`.
This method is suboptimal for small lines because it doesn't reuse read buffer.
When you want to read file by lines please avoid using `async_open`
use `LineReader` instead.
* `def __aiter__() -> LineReader` - iterator over lines.
* `def iter_chunked(chunk_size: int = 32768) -> Reader` - iterator over
chunks.
* `.file` property contains AIOFile object
Basic example:
```python
import asyncio
from pathlib import Path
from tempfile import gettempdir
from aiofile import async_open
tmp_filename = Path(gettempdir()) / "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())
await afp.write("Hello from\nasync world")
print(await afp.readline())
print(await afp.readline())
asyncio.run(main())
```
Example without context manager:
```python
import asyncio
import atexit
import os
from tempfile import mktemp
from aiofile import async_open
TMP_NAME = mktemp()
atexit.register(os.unlink, TMP_NAME)
async def main():
afp = await async_open(TMP_NAME, "w")
await afp.write("Hello")
await afp.close()
asyncio.run(main())
assert open(TMP_NAME, "r").read() == "Hello"
```
Concatenate example program (`cat`):
```python
import asyncio
import sys
from argparse import ArgumentParser
from pathlib import Path
from aiofile import async_open
parser = ArgumentParser(
description="Read files line by line using asynchronous io API"
)
parser.add_argument("file_name", nargs="+", type=Path)
async def main(arguments):
for src in arguments.file_name:
async with async_open(src, "r") as afp:
async for line in afp:
sys.stdout.write(line)
asyncio.run(main(parser.parse_args()))
```
Copy file example program (`cp`):
```python
import asyncio
from argparse import ArgumentParser
from pathlib import Path
from aiofile import async_open
parser = ArgumentParser(
description="Copying files using asynchronous io API"
)
parser.add_argument("source", type=Path)
parser.add_argument("dest", type=Path)
parser.add_argument("--chunk-size", type=int, default=65535)
async def main(arguments):
async with async_open(arguments.source, "rb") as src, \
async_open(arguments.dest, "wb") as dest:
async for chunk in src.iter_chunked(arguments.chunk_size):
await dest.write(chunk)
asyncio.run(main(parser.parse_args()))
```
Example with opening already open file pointer:
```python
import asyncio
from typing import IO, Any
from aiofile import async_open
async def main(fp: IO[Any]):
async with async_open(fp) as afp:
await afp.write("Hello from\nasync world")
print(await afp.readline())
with open("test.txt", "w+") as fp:
asyncio.run(main(fp))
```
Linux native aio doesn't support reading and writing special files
(e.g. procfs/sysfs/unix pipes/etc.), so you can perform operations with
these files using compatible context objects.
```python
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:
# Custom context should be reused
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")
)
# Copying file content line by line
async for line in src:
await dest.write(line)
asyncio.run(main())
```
### Low-level API
The `AIOFile` class is a low-level interface for asynchronous file operations, and the read and write methods accept
an `offset=0` in bytes at which the operation will be performed.
This allows you to do many independent IO operations on a once open file without moving the virtual carriage.
For example, you may make 10 concurrent HTTP requests by specifying the `Range` header, and asynchronously write
one opened file, while the offsets must either be calculated manually, or use 10 instances of `Writer` with
specified initial offsets.
In order to provide sequential reading and writing, there is `Writer`, `Reader` and `LineReader`. Keep in mind
`async_open` is not the same as AIOFile, it provides a similar interface for file operations, it simulates methods
like read or write as it is implemented in the built-in open.
```python
import asyncio
from aiofile import AIOFile
async def main():
async with AIOFile("hello.txt", 'w+') as afp:
payload = "Hello world\n"
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())
```
The Low-level API in fact is just little bit sugared `caio` API.
```python
import asyncio
from aiofile import AIOFile
async def main():
async with AIOFile("/tmp/hello.txt", 'w+') as afp:
await afp.write("Hello ")
await afp.write("world", offset=7)
await afp.fsync()
print(await afp.read())
asyncio.run(main())
```
#### `Reader` and `Writer`
When you want to read or write file linearly following example
might be helpful.
```python
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())
```
#### `LineReader` - read file line by line
LineReader is a helper that is very effective when you want to read a file
linearly and line by line.
It contains a buffer and will read the fragments of the file chunk by
chunk into the buffer, where it will try to find lines.
The default chunk size is 4KB.
```python
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")
await writer(" ")
await writer("World")
await writer("\n")
await writer("\n")
await writer("From async world")
await afp.fsync()
async for line in LineReader(afp):
print(line)
asyncio.run(main())
```
When you want to read file by lines please avoid to use `async_open`
use `LineReader` instead.
## More examples
Useful examples with `aiofile`
### Async CSV Dict Reader
```python
import asyncio
import io
from csv import DictReader
from aiofile import AIOFile, LineReader
class AsyncDictReader:
def __init__(self, afp, **kwargs):
self.buffer = io.BytesIO()
self.file_reader = LineReader(
afp, line_sep=kwargs.pop('line_sep', '\n'),
chunk_size=kwargs.pop('chunk_size', 4096),
offset=kwargs.pop('offset', 0),
)
self.reader = DictReader(
io.TextIOWrapper(
self.buffer,
encoding=kwargs.pop('encoding', 'utf-8'),
errors=kwargs.pop('errors', 'replace'),
), **kwargs,
)
self.line_num = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.line_num == 0:
header = await self.file_reader.readline()
self.buffer.write(header)
line = await self.file_reader.readline()
if not line:
raise StopAsyncIteration
self.buffer.write(line)
self.buffer.seek(0)
try:
result = next(self.reader)
except StopIteration as e:
raise StopAsyncIteration from e
self.buffer.seek(0)
self.buffer.truncate(0)
self.line_num = self.reader.line_num
return result
async def main():
async with AIOFile('sample.csv', 'rb') as afp:
async for item in AsyncDictReader(afp):
print(item)
asyncio.run(main())
```
## Troubleshooting
The caio `linux` implementation works normal for modern linux kernel versions
and file systems. So you may have problems specific for your environment.
It's not a bug and might be resolved some ways:
1. Upgrade the kernel
2. Use compatible file systems
3. Use threads based or pure python implementation.
The caio since version 0.7.0 contains some ways to do this.
1. In runtime use the environment variable `CAIO_IMPL` with
possible values:
* `linux` - use native linux kernels aio mechanism
* `thread` - use thread based implementation written in C
* `python` - use pure python implementation
2. File `default_implementation` located near `__init__.py` in caio
installation path. It's useful for distros package maintainers. This file
might contains comments (lines starts with `#` symbol) and the first line
should be one of `linux` `thread` or `python`.
3. You might manually manage contexts:
```python
import asyncio
from aiofile import async_open
from caio import linux_aio_asyncio, thread_aio_asyncio
async def main():
linux_ctx = linux_aio_asyncio.AsyncioContext()
threads_ctx = thread_aio_asyncio.AsyncioContext()
async with async_open("/tmp/test.txt", "w", context=linux_ctx) as afp:
await afp.write("Hello")
async with async_open("/tmp/test.txt", "r", context=threads_ctx) as afp:
print(await afp.read())
asyncio.run(main())
```
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1728382758.574391
aiofile-3.9.0/aiofile/__init__.py 0000644 0000000 0000000 00000001152 14701203447 013603 0 ustar 00 from .aio import AIOFile
from .utils import (
BinaryFileWrapper, FileIOWrapperBase, LineReader, Reader, TextFileWrapper,
Writer, async_open,
)
from .version import (
__author__, __version__, author_info, package_info, package_license,
project_home, team_email, version_info,
)
__all__ = (
"AIOFile",
"BinaryFileWrapper",
"FileIOWrapperBase",
"LineReader",
"Reader",
"TextFileWrapper",
"Writer",
"__author__",
"__version__",
"async_open",
"author_info",
"package_info",
"package_license",
"project_home",
"team_email",
"version_info",
)
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1728383963.708356
aiofile-3.9.0/aiofile/aio.py 0000644 0000000 0000000 00000021673 14701205734 012627 0 ustar 00 import asyncio
import os
from collections import namedtuple
from concurrent.futures import Executor
from functools import partial
from os import strerror
from pathlib import Path
from typing import (
Any, Awaitable, BinaryIO, Callable, Dict, Generator, Optional, TextIO,
TypeVar, Union,
)
from weakref import finalize
import caio
from caio.asyncio_base import AsyncioContextBase
_T = TypeVar("_T")
AIO_FILE_NOT_OPENED = -1
AIO_FILE_CLOSED = -2
FileIOType = Union[TextIO, BinaryIO]
FileMode = namedtuple(
"FileMode", (
"readable",
"writable",
"plus",
"appending",
"created",
"flags",
"binary",
),
)
def parse_mode(mode: str) -> FileMode: # noqa: C901
""" Rewritten from `cpython fileno`_
.. _cpython fileio: https://bit.ly/2JY2cnp
"""
flags = os.O_RDONLY
rwa = False
writable = False
readable = False
plus = False
appending = False
created = False
binary = False
for m in mode:
if m == "x":
rwa = True
created = True
writable = True
flags |= os.O_EXCL | os.O_CREAT
if m == "r":
if rwa:
raise Exception("Bad mode")
rwa = True
readable = True
if m == "w":
if rwa:
raise Exception("Bad mode")
rwa = True
writable = True
flags |= os.O_CREAT | os.O_TRUNC
if m == "a":
if rwa:
raise Exception("Bad mode")
rwa = True
writable = True
appending = True
flags |= os.O_CREAT | os.O_APPEND
if m == "+":
if plus:
raise Exception("Bad mode")
readable = True
writable = True
plus = True
if m == "b":
binary = True
if hasattr(os, "O_BINARY"):
flags |= os.O_BINARY
if readable and writable:
flags |= os.O_RDWR
elif readable:
flags |= os.O_RDONLY
else:
flags |= os.O_WRONLY
return FileMode(
readable=readable,
writable=writable,
plus=plus,
appending=appending,
created=created,
flags=flags,
binary=binary,
)
class AIOFile:
_file_obj: Optional[FileIOType]
_file_obj_owner: bool
_encoding: str
_executor: Optional[Executor]
mode: FileMode
__open_result: "Optional[asyncio.Future[FileIOType]]"
def __init__(
self, filename: Union[str, Path],
mode: str = "r", encoding: str = "utf-8",
context: Optional[AsyncioContextBase] = None,
executor: Optional[Executor] = None,
):
self.__context = context or get_default_context()
self.__open_result = None
self._fname = str(filename)
self._open_mode = mode
self.mode = parse_mode(mode)
self._file_obj = None
self._file_obj_owner = True
self._encoding = encoding
self._executor = executor
@classmethod
def from_fp(cls, fp: FileIOType, **kwargs: Any) -> "AIOFile":
afp = cls(fp.name, fp.mode, **kwargs)
afp._file_obj = fp
afp._open_mode = fp.mode
afp._file_obj_owner = False
return afp
def _run_in_thread(
self, func: "Callable[..., _T]", *args: Any, **kwargs: Any,
) -> "asyncio.Future[_T]":
return self.__context.loop.run_in_executor(
self._executor, partial(func, *args, **kwargs),
)
@property
def name(self) -> str:
return self._fname
@property
def loop(self) -> asyncio.AbstractEventLoop:
return self.__context.loop
@property
def encoding(self) -> str:
return self._encoding
async def open(self) -> Optional[int]:
if self._file_obj is not None:
if self._file_obj.closed:
raise asyncio.InvalidStateError("AIOFile closed")
return None
if self.__open_result is None:
self.__open_result = self._run_in_thread(
open,
self._fname,
self._open_mode,
)
self._file_obj = await self.__open_result
self.__open_result = None
return self._file_obj.fileno()
await self.__open_result
return None
def __repr__(self) -> str:
return "" % self._fname
async def close(self) -> None:
if self._file_obj is None or not self._file_obj_owner:
return
if self.mode.writable:
await self.fdsync()
await self._run_in_thread(self._file_obj.close)
def fileno(self) -> int:
if self._file_obj is None:
raise asyncio.InvalidStateError("AIOFile closed")
return self._file_obj.fileno()
def __await__(self) -> Generator[None, Any, "AIOFile"]:
yield from self.open().__await__()
return self
async def __aenter__(self) -> "AIOFile":
await self.open()
return self
def __aexit__(self, *args: Any) -> Awaitable[Any]:
return asyncio.get_event_loop().create_task(self.close())
async def read(self, size: int = -1, offset: int = 0) -> Union[bytes, str]:
data = await self.read_bytes(size, offset)
return data if self.mode.binary else self.decode_bytes(data)
async def read_bytes(self, size: int = -1, offset: int = 0) -> bytes:
if size < -1:
raise ValueError("Unsupported value %d for size" % size)
if size == -1:
size = (
await self._run_in_thread(
os.stat,
self.fileno(),
)
).st_size
return await self.__context.read(size, self.fileno(), offset)
async def write(self, data: Union[str, bytes], offset: int = 0) -> int:
if self.mode.binary:
if not isinstance(data, bytes):
raise ValueError("Data must be bytes in binary mode")
bytes_data = data
else:
if not isinstance(data, str):
raise ValueError("Data must be str in text mode")
bytes_data = self.encode_bytes(data)
return await self.write_bytes(bytes_data, offset)
def encode_bytes(self, data: str) -> bytes:
return data.encode(self._encoding)
def decode_bytes(self, data: bytes) -> str:
return data.decode(self._encoding)
async def write_bytes(self, data: bytes, offset: int = 0) -> int:
data_size = len(data)
if data_size == 0:
return 0
# data can be written partially, see write(2)
# (https://www.man7.org/linux/man-pages/man2/write.2.html)
# for example, it can happen when a disk quota or a resource limit
# is exceeded (in that case subsequent call will return a
# corresponding error) or write has been interrupted by
# an incoming signal
# behaviour here in regard to continue trying to write remaining data
# corresponds to the behaviour of io.BufferedIOBase
# (https://docs.python.org/3/library/io.html#io.BufferedIOBase.write)
# which used by object returned open() with `buffering` argument >= 1
# (effectively the default)
written = 0
while written < data_size:
res = await self.__context.write(
data[written:], self.fileno(), offset + written,
)
if res == 0:
raise RuntimeError(
"Write operation returned 0", self, offset, written,
)
elif res < 0:
# fix for linux_aio implementation bug in caio<=0.6.1
# (https://github.com/mosquito/caio/pull/7)
# and safeguard against future similar issues
errno = -res
raise OSError(errno, strerror(errno), self._fname)
written += res
return written
async def fsync(self) -> None:
return await self.__context.fsync(self.fileno())
async def fdsync(self) -> None:
return await self.__context.fdsync(self.fileno())
def truncate(self, length: int = 0) -> Awaitable[None]:
return self._run_in_thread(
os.ftruncate, self.fileno(), length,
)
ContextStoreType = Dict[asyncio.AbstractEventLoop, caio.AsyncioContext]
DEFAULT_CONTEXT_STORE: ContextStoreType = {}
def create_context(
max_requests: int = caio.AsyncioContext.MAX_REQUESTS_DEFAULT,
) -> caio.AsyncioContext:
loop = asyncio.get_event_loop()
context = caio.AsyncioContext(max_requests, loop=loop)
def finalizer() -> None:
context.close()
DEFAULT_CONTEXT_STORE.pop(context, None)
finalize(loop, finalizer)
DEFAULT_CONTEXT_STORE[loop] = context
return context
def get_default_context() -> caio.AsyncioContext:
loop = asyncio.get_event_loop()
context = DEFAULT_CONTEXT_STORE.get(loop)
if context is not None:
return context
return create_context()
././@PaxHeader 0000000 0000000 0000000 00000000026 00000000000 010213 x ustar 00 22 mtime=1597262590.0
aiofile-3.9.0/aiofile/py.typed 0000644 0000000 0000000 00000000001 13715045376 013172 0 ustar 00
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1728383963.7088778
aiofile-3.9.0/aiofile/utils.py 0000644 0000000 0000000 00000024557 14701205734 013223 0 ustar 00 import asyncio
import collections.abc
import io
import os
from abc import ABC, abstractmethod
from pathlib import Path
from types import MappingProxyType
from typing import Any, Generator, Tuple, Union
from .aio import AIOFile, FileIOType
ENCODING_MAP = MappingProxyType({
"utf-8": 4,
"utf-16": 8,
"UTF-8": 4,
"UTF-16": 8,
})
async def unicode_reader(
afp: AIOFile, chunk_size: int, offset: int, encoding: str = "utf-8",
) -> Tuple[int, str]:
if chunk_size < 0:
chunk_bytes = await afp.read_bytes(-1, offset)
return len(chunk_bytes), chunk_bytes.decode(encoding=encoding)
last_error = None
for retry in range(ENCODING_MAP.get(encoding, 4)):
chunk_bytes = await afp.read_bytes(chunk_size + retry, offset)
try:
chunk = chunk_bytes.decode(encoding=encoding)
break
except UnicodeDecodeError as e:
last_error = e
else:
raise last_error # type: ignore
chunk_size = len(chunk_bytes)
return chunk_size, chunk
class Reader(collections.abc.AsyncIterable):
__slots__ = "_chunk_size", "__offset", "file", "__lock", "encoding"
CHUNK_SIZE = 32 * 1024
def __init__(
self, aio_file: AIOFile, offset: int = 0,
chunk_size: int = CHUNK_SIZE,
):
self.__lock = asyncio.Lock()
self.__offset = int(offset)
self._chunk_size = int(chunk_size)
self.file = aio_file
self.encoding = self.file.encoding
async def read_chunk(self) -> Union[str, bytes]:
async with self.__lock:
if self.file.mode.binary:
chunk = await self.file.read_bytes(
self._chunk_size, self.__offset,
) # type: Union[str, bytes]
chunk_size = len(chunk)
else:
chunk_size, chunk = await unicode_reader(
self.file, self._chunk_size, self.__offset,
encoding=self.encoding,
)
self.__offset += chunk_size
return chunk
async def __anext__(self) -> Union[str, bytes]:
chunk = await self.read_chunk()
if not chunk:
raise StopAsyncIteration(chunk)
return chunk
def __aiter__(self) -> "Reader":
return self
class Writer:
__slots__ = "__chunk_size", "__offset", "__aio_file", "__lock"
def __init__(self, aio_file: AIOFile, offset: int = 0):
self.__offset = int(offset)
self.__aio_file = aio_file
self.__lock = asyncio.Lock()
async def __call__(self, data: Union[str, bytes]) -> None:
async with self.__lock:
if isinstance(data, str):
data = self.__aio_file.encode_bytes(data)
await self.__aio_file.write_bytes(data, self.__offset)
self.__offset += len(data)
class LineReader(collections.abc.AsyncIterable):
CHUNK_SIZE = 4192
def __init__(
self, aio_file: AIOFile, offset: int = 0,
chunk_size: int = CHUNK_SIZE, line_sep: str = "\n",
):
self.__reader = Reader(aio_file, chunk_size=chunk_size, offset=offset)
self._buffer = (
io.BytesIO() if aio_file.mode.binary else io.StringIO()
) # type: Any
self.linesep = (
aio_file.encode_bytes(line_sep)
if aio_file.mode.binary
else line_sep
)
async def readline(self) -> Union[str, bytes]:
while True:
line = self._buffer.readline()
if line and line.endswith(self.linesep):
return line
buffer_remainder = line + self._buffer.read()
self._buffer.truncate(0)
self._buffer.seek(0)
# No line in buffer, read more data
chunk = await self.__reader.read_chunk()
if not chunk:
# No more data to read, return any remaining content in the buffer
return buffer_remainder
# We have more data to read, write it to the buffer and handle it in the next iteration
self._buffer.write(buffer_remainder)
self._buffer.write(chunk)
self._buffer.seek(0)
async def __anext__(self) -> Union[bytes, str]:
line = await self.readline()
if not line:
# We are finished, close the buffer and raise StopAsyncIteration
self._buffer.close()
raise StopAsyncIteration(line)
return line
def __aiter__(self) -> "LineReader":
return self
class FileIOWrapperBase(ABC):
_READLINE_CHUNK_SIZE = 4192
def __init__(self, afp: AIOFile, *, offset: int = 0):
self._offset = offset
self._lock = asyncio.Lock()
self.file = afp
if self.file.mode.appending:
try:
self._offset = os.stat(afp.name).st_size
except FileNotFoundError:
self._offset = 0
@abstractmethod
async def read(self, length: int = -1) -> Any:
raise NotImplementedError
@abstractmethod
async def write(self, data: Any) -> int:
raise NotImplementedError
@abstractmethod
async def readline(
self, size: int = -1, newline: Any = ...,
) -> Union[str, bytes]:
raise NotImplementedError
def seek(self, offset: int) -> None:
self._offset = offset
def tell(self) -> int:
return self._offset
async def flush(self, sync_metadata: bool = False) -> None:
if sync_metadata:
await self.file.fsync()
else:
await self.file.fdsync()
async def close(self) -> None:
await self.file.close()
def __await__(self) -> Generator[None, None, "FileIOWrapperBase"]:
yield from self.file.__await__()
return self
async def __aenter__(self) -> "FileIOWrapperBase":
await self.file.open()
return self
async def __aexit__(self, *_: Any) -> None:
await self.close()
def __aiter__(self) -> LineReader:
return LineReader(self.file)
def iter_chunked(self, chunk_size: int = Reader.CHUNK_SIZE) -> Reader:
return Reader(self.file, chunk_size=chunk_size, offset=self._offset)
class BinaryFileWrapper(FileIOWrapperBase):
def __init__(self, afp: AIOFile):
if not afp.mode.binary:
raise ValueError("Expected file in binary mode")
super().__init__(afp)
async def __read(self, length: int) -> bytes:
data = await self.file.read_bytes(length, self._offset)
self._offset += len(data)
return data
async def read(self, length: int = -1) -> bytes:
async with self._lock:
return await self.__read(length)
async def write(self, data: bytes) -> int:
async with self._lock:
operation = self.file.write_bytes(data, self._offset)
self._offset += len(data)
await operation
return len(data)
async def readline(self, size: int = -1, newline: bytes = b"\n") -> bytes:
async with self._lock:
offset = self._offset
with io.BytesIO() as fp:
while True:
chunk = await self.__read(self._READLINE_CHUNK_SIZE)
if chunk:
if newline not in chunk:
fp.write(chunk)
continue
fp.write(chunk)
if 0 < size <= fp.tell():
fp.seek(size)
fp.truncate(size)
return fp.getvalue()
fp.seek(0)
line = fp.readline()
self._offset = offset + fp.tell()
return line
class TextFileWrapper(FileIOWrapperBase):
def __init__(self, afp: AIOFile):
if afp.mode.binary:
raise ValueError("Expected file in text mode")
super().__init__(afp)
self.encoding = self.file.encoding
async def __read(self, length: int) -> str:
chunk_size = 0
offset = self._offset
chunk = ""
while length < 0 or length > len(chunk):
part_offset, part = await unicode_reader(
self.file, length, offset, self.encoding,
)
if not part:
break
chunk += part
offset += part_offset
if chunk_size > length > 0:
chunk = chunk[:length]
offset = length
self._offset = offset
return chunk
async def read(self, length: int = -1) -> str:
async with self._lock:
return await self.__read(length)
async def write(self, data: str) -> int:
async with self._lock:
data_bytes = data.encode(self.encoding)
operation = self.file.write_bytes(data_bytes, self._offset)
self._offset += len(data_bytes)
await operation
return len(data_bytes)
async def readline(self, size: int = -1, newline: str = "\n") -> str:
async with self._lock:
offset = self._offset
with io.StringIO() as fp:
while True:
chunk = await self.__read(self._READLINE_CHUNK_SIZE)
if chunk:
if newline not in chunk:
fp.write(chunk)
continue
fp.write(chunk)
if 0 < size <= fp.tell():
fp.seek(size)
fp.truncate(size)
return fp.getvalue()
fp.seek(0)
line = fp.readline()
self._offset = offset + len(
line.encode(encoding=self.encoding),
)
return line
def async_open(
file_specifier: Union[str, Path, FileIOType],
mode: str = "r", *args: Any, **kwargs: Any,
) -> Union[BinaryFileWrapper, TextFileWrapper]:
if isinstance(file_specifier, (str, Path)):
afp = AIOFile(str(file_specifier), mode, *args, **kwargs)
else:
if args:
raise ValueError("Arguments denied when IO[Any] opening.")
afp = AIOFile.from_fp(file_specifier, **kwargs)
if not afp.mode.binary:
return TextFileWrapper(afp)
return BinaryFileWrapper(afp)
__all__ = (
"BinaryFileWrapper",
"FileIOWrapperBase",
"LineReader",
"Reader",
"TextFileWrapper",
"Writer",
"async_open",
"unicode_reader",
)
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1728383963.709268
aiofile-3.9.0/aiofile/version.py 0000644 0000000 0000000 00000000743 14701205734 013537 0 ustar 00 import importlib.metadata
package_metadata = importlib.metadata.metadata("aiofile")
__author__ = package_metadata["Author"]
__version__ = package_metadata["Version"]
author_info = [(package_metadata["Author"], package_metadata["Author-email"])]
package_info = package_metadata["Summary"]
package_license = package_metadata["License"]
project_home = package_metadata["Home-page"]
team_email = package_metadata["Author-email"]
version_info = tuple(map(int, __version__.split(".")))
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1728383963.7103572
aiofile-3.9.0/pyproject.toml 0000644 0000000 0000000 00000004576 14701205734 013014 0 ustar 00 [tool.poetry]
name = "aiofile"
version = "3.9.0"
description = "Asynchronous file operations."
license = "Apache-2.0"
authors = ["Dmitry Orlov "]
readme = "README.md"
homepage = "http://github.com/mosquito/aiofile"
keywords = ["aio", "python", "asyncio", "fileio", "io"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: Developers",
"Intended Audience :: Education",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Natural Language :: Russian",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS :: MacOS X",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development :: Libraries",
"Topic :: System",
"Topic :: System :: Operating System",
]
packages = [
{ include = "aiofile" },
]
[tool.poetry.dependencies]
python = ">=3.8,<4"
caio = "~0.9.0"
[tool.poetry.group.dev.dependencies]
markdown-pytest = "^0.3.2"
pytest = ">=8.2.0,<8.3.0"
aiomisc-pytest = "^1.2.1"
pytest-cov = "^5.0.0"
coveralls = "<4"
pylama = "^8.4.1"
setuptools = "^75.1.0"
mypy = "^1.11.2"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.mypy]
check_untyped_defs = true
disallow_any_generics = false
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
follow_imports = "silent"
no_implicit_reexport = true
strict_optional = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = true
files = [
"aiofile",
"tests",
]
[[tool.mypy.overrides]]
module = ["tests.*"]
check_untyped_defs = true
disallow_incomplete_defs = false
disallow_untyped_calls = false
disallow_untyped_decorators = false
disallow_untyped_defs = false
warn_unused_ignores = false
[tool.pylama]
skip = ["*env*", ".*", "*build*"]
[tool.pylama.pycodestyle]
max_line_length = 80
aiofile-3.9.0/PKG-INFO 0000644 0000000 0000000 00000033336 00000000000 011127 0 ustar 00 Metadata-Version: 2.1
Name: aiofile
Version: 3.9.0
Summary: Asynchronous file operations.
Home-page: http://github.com/mosquito/aiofile
License: Apache-2.0
Keywords: aio,python,asyncio,fileio,io
Author: Dmitry Orlov
Author-email: me@mosquito.su
Requires-Python: >=3.8,<4
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Natural Language :: English
Classifier: Natural Language :: Russian
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System
Classifier: Topic :: System :: Operating System
Requires-Dist: caio (>=0.9.0,<0.10.0)
Description-Content-Type: text/markdown
# AIOFile
[](https://github.com/mosquito/aiofile/actions?query=branch%3Amaster) [](https://pypi.python.org/pypi/aiofile/) [](https://pypi.python.org/pypi/aiofile/) [](https://pypi.python.org/pypi/aiofile/) [](https://pypi.python.org/pypi/aiofile/) [](https://coveralls.io/github/mosquito/aiofile?branch=master)
Real asynchronous file operations with asyncio support.
## Status
Development - Stable
## Features
* Since version 2.0.0 using [caio](https://pypi.org/project/caio), which contains linux `libaio` and two
thread-based implementations (c-based and pure-python).
* AIOFile has no internal pointer. You should pass `offset` and
`chunk_size` for each operation or use helpers (Reader or Writer).
The simplest way is to use `async_open` for creating object with
file-like interface.
* For Linux using implementation based on [libaio](https://pagure.io/libaio).
* For POSIX (MacOS X and optional Linux) using implementation
based on [threadpool](https://github.com/mbrossard/threadpool/).
* Otherwise using pure-python thread-based implementation.
* Implementation chooses automatically depending on system compatibility.
## Limitations
* Linux native AIO implementation is not able to open special files.
Asynchronous operations against special fs like `/proc/` `/sys/` are not
supported by the kernel. It's not a aiofile's or caio issue.
In these cases, you might switch to thread-based implementations
(see [Troubleshooting](#troubleshooting) section).
However, when used on supported file systems, the linux implementation has a
smaller overhead and is preferred but it's not a silver bullet.
## Code examples
All code examples require Python 3.6+.
### High-level API
#### `async_open` helper
Helper mimics python file-like objects, it returns file-like
objects with similar but async methods.
Supported methods:
* `async def read(length = -1)` - reading chunk from file, when length is
`-1`, will be reading file to the end.
* `async def write(data)` - writing chunk to file
* `def seek(offset)` - setting file pointer position
* `def tell()` - returns current file pointer position
* `async def readline(size=-1, newline="\n")` - read chunks until
newline or EOF. Since version 3.7.0 `__aiter__` returns `LineReader`.
This method is suboptimal for small lines because it doesn't reuse read buffer.
When you want to read file by lines please avoid using `async_open`
use `LineReader` instead.
* `def __aiter__() -> LineReader` - iterator over lines.
* `def iter_chunked(chunk_size: int = 32768) -> Reader` - iterator over
chunks.
* `.file` property contains AIOFile object
Basic example:
```python
import asyncio
from pathlib import Path
from tempfile import gettempdir
from aiofile import async_open
tmp_filename = Path(gettempdir()) / "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())
await afp.write("Hello from\nasync world")
print(await afp.readline())
print(await afp.readline())
asyncio.run(main())
```
Example without context manager:
```python
import asyncio
import atexit
import os
from tempfile import mktemp
from aiofile import async_open
TMP_NAME = mktemp()
atexit.register(os.unlink, TMP_NAME)
async def main():
afp = await async_open(TMP_NAME, "w")
await afp.write("Hello")
await afp.close()
asyncio.run(main())
assert open(TMP_NAME, "r").read() == "Hello"
```
Concatenate example program (`cat`):
```python
import asyncio
import sys
from argparse import ArgumentParser
from pathlib import Path
from aiofile import async_open
parser = ArgumentParser(
description="Read files line by line using asynchronous io API"
)
parser.add_argument("file_name", nargs="+", type=Path)
async def main(arguments):
for src in arguments.file_name:
async with async_open(src, "r") as afp:
async for line in afp:
sys.stdout.write(line)
asyncio.run(main(parser.parse_args()))
```
Copy file example program (`cp`):
```python
import asyncio
from argparse import ArgumentParser
from pathlib import Path
from aiofile import async_open
parser = ArgumentParser(
description="Copying files using asynchronous io API"
)
parser.add_argument("source", type=Path)
parser.add_argument("dest", type=Path)
parser.add_argument("--chunk-size", type=int, default=65535)
async def main(arguments):
async with async_open(arguments.source, "rb") as src, \
async_open(arguments.dest, "wb") as dest:
async for chunk in src.iter_chunked(arguments.chunk_size):
await dest.write(chunk)
asyncio.run(main(parser.parse_args()))
```
Example with opening already open file pointer:
```python
import asyncio
from typing import IO, Any
from aiofile import async_open
async def main(fp: IO[Any]):
async with async_open(fp) as afp:
await afp.write("Hello from\nasync world")
print(await afp.readline())
with open("test.txt", "w+") as fp:
asyncio.run(main(fp))
```
Linux native aio doesn't support reading and writing special files
(e.g. procfs/sysfs/unix pipes/etc.), so you can perform operations with
these files using compatible context objects.
```python
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:
# Custom context should be reused
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")
)
# Copying file content line by line
async for line in src:
await dest.write(line)
asyncio.run(main())
```
### Low-level API
The `AIOFile` class is a low-level interface for asynchronous file operations, and the read and write methods accept
an `offset=0` in bytes at which the operation will be performed.
This allows you to do many independent IO operations on a once open file without moving the virtual carriage.
For example, you may make 10 concurrent HTTP requests by specifying the `Range` header, and asynchronously write
one opened file, while the offsets must either be calculated manually, or use 10 instances of `Writer` with
specified initial offsets.
In order to provide sequential reading and writing, there is `Writer`, `Reader` and `LineReader`. Keep in mind
`async_open` is not the same as AIOFile, it provides a similar interface for file operations, it simulates methods
like read or write as it is implemented in the built-in open.
```python
import asyncio
from aiofile import AIOFile
async def main():
async with AIOFile("hello.txt", 'w+') as afp:
payload = "Hello world\n"
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())
```
The Low-level API in fact is just little bit sugared `caio` API.
```python
import asyncio
from aiofile import AIOFile
async def main():
async with AIOFile("/tmp/hello.txt", 'w+') as afp:
await afp.write("Hello ")
await afp.write("world", offset=7)
await afp.fsync()
print(await afp.read())
asyncio.run(main())
```
#### `Reader` and `Writer`
When you want to read or write file linearly following example
might be helpful.
```python
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())
```
#### `LineReader` - read file line by line
LineReader is a helper that is very effective when you want to read a file
linearly and line by line.
It contains a buffer and will read the fragments of the file chunk by
chunk into the buffer, where it will try to find lines.
The default chunk size is 4KB.
```python
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")
await writer(" ")
await writer("World")
await writer("\n")
await writer("\n")
await writer("From async world")
await afp.fsync()
async for line in LineReader(afp):
print(line)
asyncio.run(main())
```
When you want to read file by lines please avoid to use `async_open`
use `LineReader` instead.
## More examples
Useful examples with `aiofile`
### Async CSV Dict Reader
```python
import asyncio
import io
from csv import DictReader
from aiofile import AIOFile, LineReader
class AsyncDictReader:
def __init__(self, afp, **kwargs):
self.buffer = io.BytesIO()
self.file_reader = LineReader(
afp, line_sep=kwargs.pop('line_sep', '\n'),
chunk_size=kwargs.pop('chunk_size', 4096),
offset=kwargs.pop('offset', 0),
)
self.reader = DictReader(
io.TextIOWrapper(
self.buffer,
encoding=kwargs.pop('encoding', 'utf-8'),
errors=kwargs.pop('errors', 'replace'),
), **kwargs,
)
self.line_num = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.line_num == 0:
header = await self.file_reader.readline()
self.buffer.write(header)
line = await self.file_reader.readline()
if not line:
raise StopAsyncIteration
self.buffer.write(line)
self.buffer.seek(0)
try:
result = next(self.reader)
except StopIteration as e:
raise StopAsyncIteration from e
self.buffer.seek(0)
self.buffer.truncate(0)
self.line_num = self.reader.line_num
return result
async def main():
async with AIOFile('sample.csv', 'rb') as afp:
async for item in AsyncDictReader(afp):
print(item)
asyncio.run(main())
```
## Troubleshooting
The caio `linux` implementation works normal for modern linux kernel versions
and file systems. So you may have problems specific for your environment.
It's not a bug and might be resolved some ways:
1. Upgrade the kernel
2. Use compatible file systems
3. Use threads based or pure python implementation.
The caio since version 0.7.0 contains some ways to do this.
1. In runtime use the environment variable `CAIO_IMPL` with
possible values:
* `linux` - use native linux kernels aio mechanism
* `thread` - use thread based implementation written in C
* `python` - use pure python implementation
2. File `default_implementation` located near `__init__.py` in caio
installation path. It's useful for distros package maintainers. This file
might contains comments (lines starts with `#` symbol) and the first line
should be one of `linux` `thread` or `python`.
3. You might manually manage contexts:
```python
import asyncio
from aiofile import async_open
from caio import linux_aio_asyncio, thread_aio_asyncio
async def main():
linux_ctx = linux_aio_asyncio.AsyncioContext()
threads_ctx = thread_aio_asyncio.AsyncioContext()
async with async_open("/tmp/test.txt", "w", context=linux_ctx) as afp:
await afp.write("Hello")
async with async_open("/tmp/test.txt", "r", context=threads_ctx) as afp:
print(await afp.read())
asyncio.run(main())
```