Changelog
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.1.1 - 2026-10-16
Changed
- Reading a directory as a file (
read_bytes(),read_text(), a read ofopen("rb")) raisesIsADirectoryError(EISDIR, with the remote path asfilename) against servers that open a directory for reading, as OpenSSH does, on both backends. Against OpenSSH for Windows an unsized read returnedb""(""in text mode); against OpenSSH on Linux, and for a sized read on Windows, it raised anOSError("Failure")with noerrno.open()itself still succeeds on those servers. Code that caught the bareOSErrorstill catches it; code that read a directory and got empty content now gets the error.
0.1.0 - 2026-10-14
The first release of sftppath: the SFTP support of pathlib-next 0.9.14
(pathlib_next.uri.schemes.sftp) as a distribution of its own, with a path that
needs no URI. It needs Python 3.9 or newer and pathapi>=0.1.0,<0.2.
Neither SSH library is required to import it: the extras paramiko
(paramiko>=5.0,<6) and asyncssh (asyncssh>=2.20.0, below 3) add one, and
uri (pathapi-uri>=0.1.0,<0.2) adds the sftp: scheme. With neither SSH library a
path can still be built, joined and compared, and its first request raises an
ImportError that names both extras; naming SFTPBackend or AsyncSSHBackend
without its library raises one that names its extra. Under Changed, the
migration from pathlib_next.uri.schemes.sftp comes in this order: the table of
names, the table of extras, then every change of behaviour with what to do about
it.
It is built on pathapi, which was published as pathlib-next up to 0.10.0 (import
name pathlib_next); the old names below are the ones before the move.
Added
SFTPPath, apathapi.Pathfor a directory tree on an SFTP server, built from keywords:SFTPPath("/srv/x", host="h", port=2222, user="u", password="p"). Nothing connects until the first request.backend=shares one connection between paths built separately; a path joined from another keeps its backend.SFTPBackend(paramiko),AsyncSSHBackend(asyncssh),BaseSFTPBackendandCheckFileSFTPBackend, each the connections to one server and account. Every backend is a context manager,close()returns when the connections are closed, and may be called twice.SFTPEndpoint,DEFAULT_PORTandDEFAULT_SSH_CONFIGare exported too.sftppath.exceptions:SFTPPathError, apathapi.PathError, withSFTPAuthenticationError(alsoPermissionError) andSFTPHostKeyError(alsoConnectionError).sftppath.uri:SFTPURIPath, thesftp:scheme ofuripath, found byuripaththrough the entry pointsftpof the groupuripath.schemes, andSFTPURIBackend, which builds the backend of each server a URI names. Importingsftppath.uriwithouturipathraises anImportErrorthat namespip install "sftppath[uri]";import sftppathnever importsuripath.SFTPPath.is_local(): true for a loopback address,localhostand a backend with no host, soPathSyncer(quick_check=True)applies to a real server.sftppath.__version__, read from the installed metadata.
Changed
- The names.
pathlib_next.uri.schemes.sftpis gone with no alias.
| Was | Now |
|---|---|
SftpPath (a UriPath) |
sftppath.SFTPPath for a path built from keywords; sftppath.uri.SFTPURIPath for URIPath("sftp://...") |
SftpBackend |
sftppath.SFTPBackend |
AsyncsshSftpBackend |
sftppath.AsyncSSHBackend |
BaseSftpBackend |
sftppath.BaseSFTPBackend |
CheckFileSftpBackend |
sftppath.CheckFileSFTPBackend |
SftpAuthenticationError, SftpHostKeyError |
sftppath.SFTPAuthenticationError, sftppath.SFTPHostKeyError (in sftppath.exceptions) |
_DEFAULT_SSH_CONFIG |
sftppath.DEFAULT_SSH_CONFIG |
PATHLIB_NEXT_SFTP_BACKEND |
SFTPPATH_BACKEND; the old variable is not read |
the logger pathlib_next.sftp |
the logger sftppath |
the entry-point group pathlib_next.schemes, entry sftp |
the group uripath.schemes, entry sftp = "sftppath.uri:SFTPURIPath" |
- What to install where
pathlib-nexthad an extra.pathlib-next0.10 has nosftporsftp-asyncextra.
| Was | Install now |
|---|---|
pathlib-next[sftp] |
sftppath[paramiko] |
pathlib-next[sftp-async] |
sftppath[asyncssh] |
either, for UriPath("sftp://...") |
add sftppath[uri] (or install uripath beside it) |
- A backend is the connections to one server and account. Was: one backend
served every host a path named, and
client(source),opts(source)andtransport(source)took theSourceof the URI; a backend was built with its options only (SftpBackend(connect_opts, hostkeypolicy, ...)). Now the host, port, user and password are arguments of the backend (SFTPBackend("h", 2222, user="u", password="p", connect_opts=...),AsyncSSHBackend("h", ...)), andclient(),opts()andtransport()take nothing. ABaseSFTPBackendsubclass overridesclient(self). For URI paths, givebackend=anSFTPURIBackend(SFTPBackend, connect_opts=...)(the options of the backend class; the user, password and port come from the URI) or aBaseSFTPBackendfor the one server.BaseSftpBackend.default()and thedefault(ssh_config=)classmethods are removed. - A relative or empty path is read from the server root. Was:
sftp://host(no path) listed the home directory of the account. Now it is/, andSFTPPath("x", host=h)is/x. Name the directory you mean (/home/user). SFTPPath.readlink()returns a path on the same backend, so dot segments of the target collapse as they do forpathlib. TheSFTPURIPathkeeps the text verbatim in.path.SFTPPath.iterdir()yields children without the attributes the listing carried, sois_dir()on each costs a request;walk(),glob()andrm(recursive=True)still use the listing. The children anSFTPURIPathyields carry them, as before.- An error that has a file name and no second one prints only the first; it
printed
-> Noneafter it.