bagof-paths
One path API for local files and the cloud.
Wrap a path or a URL, then use it like pathlib.Path. The same code reads a
local file, an object on S3, or anything the underlying libraries reach.
from bagof.paths import Path
Path("/data/train.zarr") # a local file
Path("s3://my-bucket/train.zarr") # an object on S3
Both give you the same methods: read_bytes, exists, iterdir, /, and
the rest of the pathlib surface.
>>> from bagof.paths import Path
>>> p = Path("/data/sets/train.zarr")
>>> p.name
'train.zarr'
>>> p.parent
Path('/data/sets')
>>> p / "chunks"
Path('/data/sets/train.zarr/chunks')
Features
- One API for every path. Local files, cloud storage, and unknown backends all use the same methods.
- Missing methods are filled in. When a library lacks a method,
bagof.pathsbuilds it from simpler ones. It reads text from bytes, and copies a folder by copying its files. - One error to catch. An operation that cannot work raises a single
UnsupportedPathOperation, the same for every library. - Async support.
AsyncPathgives you the same methods withawait. - No required dependency. Local paths use only the standard library.
Installation
pip install bagof-paths # local paths
pip install bagof-paths[upath] # add remote paths via universal-pathlib
pip install bagof-paths[cloud] # add cloud paths via cloudpathlib
A remote store also needs its own library, such as s3fs for s3:// or
cloudpathlib[s3].
The same code, local or remote
Path reads the start of the string to choose a backend. A plain path is a
local file. A URL uses universal-pathlib, or cloudpathlib if you have it.
from bagof.paths import Path
def load(location: str) -> bytes:
return Path(location).read_bytes()
load("/data/train.bin")
load("s3://my-bucket/train.bin")
Credentials
Pass connection details with storage_options. They go straight to the
backend.
from bagof.paths import Path
Path(
"s3://my-bucket/train.bin",
storage_options={"key": "AKIA...", "secret": "...", "endpoint_url": "..."},
)
Leave storage_options off to use ambient credentials, such as environment
variables, ~/.aws/config, or an instance role.
Set defaults once for a scheme with set_storage_options, and a per-call
storage_options overrides them key by key.
from bagof.paths import set_storage_options
set_storage_options("s3", {"endpoint_url": "https://minio.local"})
Async
AsyncPath turns the methods that touch storage into coroutines. The methods
that only describe a path (name, parent, /) stay synchronous.
import asyncio
from bagof.paths import AsyncPath
async def main() -> None:
p = AsyncPath("s3://my-bucket/train.bin")
if await p.exists():
data = await p.read_bytes()
asyncio.run(main())
For a cloud store whose library speaks async (such as s3:// through s3fs),
AsyncPath talks to it directly, with no threads. For every other path it runs
the synchronous driver in a worker thread, so the same code works either way.
Learn more
See how it compares to pathlib, UPath, and AnyPath.