A robust, extensible pathlib-like base for any resource addressable as a
path. Same method names, signatures, semantics and exception types as
pathlib.Path wherever a pathlib.Path equivalent exists: write code once
against Path and it runs against your local disk, an in-memory tree, an FTP
server or the members of a zip or tar file, and against any path class built on
it. It has no dependencies. Deliberate differences from pathlib are listed in
Divergences from pathlib.
pathapi was published as pathlib-next up to 0.10.0; pip install pathlib-next
now installs a shim that depends on this package. See the changelog for the
migration from pathlib_next.
| Class | Read | Write | List | Stat | mkdir | Delete | rename |
|---|---|---|---|---|---|---|---|
local path (LocalPath) |
Yes | Yes | Yes | Yes | Yes | Yes | Yes |
in-memory (MemPath) |
Yes | Yes | Yes | Yes | Yes | Yes | No (move() copies) |
FTP and FTPS (FTPPath) |
Yes | Yes | Yes (MLSD, NLST fallback) | Yes | Yes | Yes | Yes |
zip members (ZipPath) |
Yes | Local archive only | Yes | Yes | Local archive only | Local archive only | Local archive only |
tar members (TarPath, incl. gz/bz2/xz) |
Yes | No | Yes | Yes | No | No | No |
ArchivePath |
As ZipPath/TarPath for the format it detects |
Every class gets glob(), walk(), copy()/move(), rm() and PathSyncer
from the base classes; LocalPath and the other classes replace some of them
where the backend has a faster way.
- One path interface over local files, in-memory trees, FTP servers and
archive members, and over any class built on
Path. MemPath: a virtual filesystem for tests, mocks and transient data.FTPPath,ZipPath,TarPath,ArchivePath: an FTP server and the members of a zip or tar file as paths, on the standard library alone.pathapi.http:HTTPClientand a transport contract for HTTP requests on the standard library (redirects that never carryAuthorizationto another origin, TLS verified by default, aproxies=provider); another HTTP library plugs in as a transport through thepathapi.http_transportsentry-point group.AsyncHTTPClientis the same client for an event loop, over async transports registered inpathapi.http_async_transports.PathSyncer: one-way, content-compared tree sync between any two implementations, with dry runs and event hooks.- Extensible: subclass
Pathfor any resource, and verify it with the bundled pytest contracts.
Paths for other places are separate distributions built on this one:
| Distribution | Provides |
|---|---|
uripath |
URIPath: one class for any URI, with the schemes file:, data:, ftp:, zip:, tar:, http: and dav:, and the uripath command |
pip install pathapiThere are no extras and no dependencies: everything above runs on the standard
library. pytest is needed only to import pathapi.testing and
pathapi.http.testing.
Local filesystem, as pathlib.Path:
from pathapi import Path
p = Path("data") / "report.txt"
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("hello")
print(p.read_text())In memory, no disk I/O:
from pathapi.mempath import MemPath
p = MemPath("/config/settings.json")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text('{"debug": true}')
print([child.name for child in p.parent.iterdir()])Archive members: the segments are the member path and the archive is any path:
import zipfile
from pathapi import ZipPath
with zipfile.ZipFile("backup.zip", "w") as archive:
archive.writestr("etc/config.ini", "[main]\n")
member = ZipPath("etc/config.ini", archive="backup.zip")
print(member.read_text())
print([child.name for child in member.parent.iterdir()])FTP: nothing connects until the first operation:
from pathapi import FTPPath
for child in FTPPath("/pub", host="ftp.example.com", user="anonymous").iterdir():
print(child.name, child.stat().st_size)Copy and sync across classes:
from pathapi import Path
from pathapi.mempath import MemPath
from pathapi.sync import PathSyncer
source = MemPath("/site")
(source / "css").mkdir(parents=True)
(source / "index.html").write_text("<h1>hi</h1>")
PathSyncer(remove_missing=True).sync(source, Path("site-copy"))
print(sorted(p.name for p in Path("site-copy").iterdir()))| Module | Purpose |
|---|---|
pathapi.path |
Pathname/Path base classes and the derived operations |
pathapi.fspath |
LocalPath, PosixPathname, WindowsPathname |
pathapi.mempath |
MemPath in-memory filesystem |
pathapi.ftp |
FTPPath, FTPBackend (FTP and FTPS on ftplib) |
pathapi.archive |
ZipPath, TarPath, ArchivePath (members of a zip or tar file), make_archive, unpack_archive |
pathapi.http |
HTTPClient, HTTPTransport, HTTPResponse, AsyncHTTPClient, ThreadedTransport (HTTP requests on http.client, and from an event loop) |
pathapi.protocols |
Stat, Chmod, BinaryOpen, NativeChecksum |
pathapi.sync |
PathSyncer, SyncEvent, PathAndStat |
pathapi.glob |
the glob engine behind Path.glob() |
pathapi.checksum |
md5, sha256, stream, native |
pathapi.providers |
the helpers a class built on Path calls |
pathapi.exceptions |
PathError, NonRelativePatternError |
pathapi.testing |
pytest contracts for custom implementations |
pathapi.http.testing |
testing support (not stable API): the pytest suite that holds an HTTP transport to the promises of the built-in one |
The full reference is on the documentation site;
an agent-oriented API summary ships inside the package as
pathapi/AGENTS.md.
AsyncPath(path) wraps any pathapi path for use inside an event loop: the
pure members stay plain, every method that does I/O is a coroutine of the same
name, listings are async iterators and open() gives an AsyncFile. Each call
runs the sync method on a worker thread, so every backend works at once and the
loop is never blocked; it is not a native async client.
import asyncio
from pathapi import AsyncPath, LocalPath
async def main():
root = AsyncPath(LocalPath("."))
async for entry in root.iterdir():
print(entry.name, await entry.is_dir())
asyncio.run(main())Subclass Path for a resource of your own (MemPath is the reference), as
worked through in
Extending.
pathapi.testing provides the pytest contracts (PurePathContract,
ReadPathContract, PathContract, AsyncPathContract) and populate_fixture_tree() that every
class of the package and of the distributions above is verified with.
Python 3.9 through 3.14. CI runs the full suite on Ubuntu for every version
from 3.9 to 3.14, on Windows and macOS for 3.9 and 3.14, and with nothing but
pytest and pyftpdlib installed on 3.9 and 3.14.
pip install -e ".[dev]"
python -m pytest -q -rsTests that need an optional package are skipped when it is missing, so check
the skip list (-rs). Contributor notes (virtual environments, formatting, CI,
releases) are in the repository's
AGENTS.md;
benchmarks are described in
Benchmarks. To preview
the documentation: pip install -e ".[docs]" then mkdocs serve.
Changes are recorded in the changelog.
- Move the
[Unreleased]entries ofCHANGELOG.mdunder a new## [x.y.z] - <date>heading and add its link definition at the bottom. - Bump
versioninpyproject.tomlin the same commit. - Before 1.0 the minor version changes only when the documented API breaks; new methods, new optional arguments and fixes are patch releases.
- Run the full suite on the oldest and the newest supported Python,
mkdocs build --strictandpython -m build. - Push
main, then thev*tag: the release workflow tests, builds and publishes to PyPI and creates the GitHub release.
MIT; see LICENSE.