Skip to main content

📁 Async pathlib for Python

Project description

📁 Async pathlib for Python

aiopath is a complete implementation of Python's pathlib that's compatible with asyncio and the async/await syntax.

All I/O performed by aiopath is asynchronous and awaitable.

Use case

If you're writing asynchronous Python code and want to take advantage of pathlib's conveniences, but don't want to mix blocking and non-blocking I/O, then you can reach for aiopath.

For example, if you're writing an asynchronous web scraping script, you might want to make several concurrent requests to websites and write the content in the responses to secondary storage:

from typing import List
from asyncio import run, gather

from aiohttp import ClientSession
from aiopath import AsyncPath


async def save_page(url: str, name: str):
  path = AsyncPath(name)
  
  if await path.exists():
    return

  async with ClientSession() as session:
    response = await session.get(url)
    content: bytes = await response.read()

  await path.write_bytes(content)


async def main():
  urls: List[str] = [
    'https://example.com',
    'https://github.com/alexdelorenzo/aiopath',
    'https://alexdelorenzo.dev',
    'https://dupebot.firstbyte.dev'
  ]

  scrapers = (
    save_page(url, f"{index}.html")
    for index, url in enumerate(urls)
  )
  
  await gather(*scrapers)


run(main())

If you used pathlib instead of aiopath in the example above, some tasks would block upon writing to the disk, and the other tasks making network connections would be forced to pause while the disk is accessed.

By using aiopath in the example above, the script can access the network and disk concurrently.

Implementation

aiopath is a direct reimplementation of CPython's pathlib.py and shares some of its code. aiopath's class hierarchy directly matches the one from pathlib, where Path inherits from PurePath, AsyncPath inherits from AsyncPurePath, and so on.

With aiopath, methods that perform I/O are asynchronous and awaitable, and methods that perform I/O and return iterators in pathlib now return async generators. aiopath goes one step further, and wraps os.scandir() and DirEntry to make AsyncPath.glob() completely async.

aiopath is typed with Python type annotations, and it takes advantage of libaio for async I/O on Linux.

Usage

aiopath's API directly matches pathlib, so check out the standard library documentation for PurePath and Path.

Running examples

To run the following examples with top-level await expressions, launch an asynchronous Python REPL using python3 -m asyncio or an IPython shell.

You'll also need to install asynctempfile via PyPI, like so python3 -m pip install asynctempfile.

Basic

All of pathlib.Path's methods that perform synchronous I/O are reimplemented as asynchronous methods. PurePath methods are not asynchronous because they don't perform I/O.

from pathlib import Path

from asynctempfile import NamedTemporaryFile
from aiopath import AsyncPath


async with NamedTemporaryFile() as temp:
  path = Path(temp.name)
  apath = AsyncPath(temp.name)

  # check existence
  ## sync
  assert path.exists()
  ## async
  assert await apath.exists()

  # check if file
  ## sync
  assert path.is_file()
  ## async
  assert await apath.is_file()

  # touch
  path.touch()
  await apath.touch()
  
  # PurePath methods are not async
  assert path.is_absolute() == apath.is_absolute()
  assert path.as_uri() == apath.as_uri()

  # read and write text
  text: str = "example"
  await apath.write_text(text)
  assert await apath.read_text() == text

assert not path.exists()
assert not await apath.exists()

You can convert pathlib.Path objects to aiopath.AsyncPath objects, and vice versa:

from pathlib import Path
from aiopath import AsyncPath


home: Path = Path.home()
ahome: AsyncPath = AsyncPath(home)
path: Path = Path(ahome)

assert isinstance(home, Path)
assert isinstance(ahome, AsyncPath)
assert isinstance(path, Path)

# AsyncPath and Path objects can point to the same file
assert str(home) == str(ahome) == str(path)

# but AsyncPath and Path objects are not equivalent
assert not home == ahome

Opening a file

You can get an asynchronous file-like object handle by using asynchronous context managers.

AsyncPath.open()'s async context manager yields an aiofile.AIOFile object.

from asynctempfile import NamedTemporaryFile
from aiopath import AsyncPath


text: str = 'example'

# you can access a file with async context managers
async with NamedTemporaryFile() as temp:
  path = AsyncPath(temp.name)

  async with path.open(mode='w') as file:
    await file.write(text)
  
  async with path.open(mode='r') as file:
    result: str = await file.read()

  assert result == text
  
# or you can use the read/write convenience methods
async with NamedTemporaryFile() as temp:
  path = AsyncPath(temp.name)

  await path.write_text(text)
  result: str = await path.read_text()
  assert result == text
  
  content: bytes = text.encode()

  await path.write_bytes(content)
  result: bytes = await path.read_bytes()
  assert result == content

Globbing

aiopath implements pathlib globbing using async I/O and async generators.

from typing import List
from aiopath import AsyncPath


home: AsyncPath = await AsyncPath.home()

async for path in home.glob('*'):
  assert isinstance(path, AsyncPath)
  print(path)

downloads: AsyncPath = home / 'Downloads'

if await downloads.exists():
  # this might take a while
  paths: List[AsyncPath] = \
    [path async for path in downloads.glob('**/*')]

Installation

Dependencies

  • A POSIX compliant OS, or Windows
  • Python 3.7+
  • requirements.txt

Linux dependencies

If you're using a 4.18 or newer kernel and have libaio installed, aiopath will use it via aiofile. You can install libaio on Debian/Ubuntu like so:

$ sudo apt install libaio1

PyPI

$ python3 -m pip install aiopath

GitHub

$ python3 -m pip install -r requirements.txt
$ python3 setup.py install

Support

Want to support this project and other open-source projects like it?

Buy Me A Coffee

License

See LICENSE. If you'd like to use this project with a different license, please get in touch.

Credit

See CREDIT.md.

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

aiopath-0.5.12.tar.gz (18.7 kB view details)

Uploaded Source

Built Distribution

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

aiopath-0.5.12-py2.py3-none-any.whl (17.9 kB view details)

Uploaded Python 2Python 3

File details

Details for the file aiopath-0.5.12.tar.gz.

File metadata

  • Download URL: aiopath-0.5.12.tar.gz
  • Upload date:
  • Size: 18.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.10.0b3+

File hashes

Hashes for aiopath-0.5.12.tar.gz
Algorithm Hash digest
SHA256 de2f9401dd098b9a279afb265df5a6eb5b78f39c2c4f3871f671fc1dd4b21d64
MD5 f9b0e309a709aff6540c6aad63092401
BLAKE2b-256 c14f25859633758f086dd213cb34b84236fea2e92e8d0e44425b82dc51638a66

See more details on using hashes here.

File details

Details for the file aiopath-0.5.12-py2.py3-none-any.whl.

File metadata

  • Download URL: aiopath-0.5.12-py2.py3-none-any.whl
  • Upload date:
  • Size: 17.9 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.10.0b3+

File hashes

Hashes for aiopath-0.5.12-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 9d85fd17ef1cbc75c0e81619acb3a1632cf5e15ead459e57fef5ec85e0b7a1da
MD5 ca5b26cd10aaa0c60aefe4f048d52b23
BLAKE2b-256 2a36f18ac0c3c6996b095e8b98f884f8d445aeca38db1341f2ec04f9792e0166

See more details on using hashes here.

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