Skip to content

SFTP paths

SFTPPath is a pathapi.Path for a directory tree on an SFTP server. It supports what pathlib.Path does on a file system (open, read_text, write_bytes, iterdir, glob, stat, mkdir, unlink, rmdir, rename, replace, chmod, symlink_to, readlink, ...) and what pathapi adds (walk, copy, move, rm, PathSyncer).

Naming the server

The endpoint is part of the path: pass it as keywords, or share a backend.

from sftppath import SFTPPath

SFTPPath("/srv/x", host="h")                                  # the account of the ssh_config
SFTPPath("/srv/x", host="h", port=2222, user="u", password="p")
SFTPPath("/srv/x", host="h", ssh_config=None, timeout=10)     # no ~/.ssh/config

host may be a name from your ~/.ssh/config: its HostName, Port, User, IdentityFile and ProxyCommand apply where the call names none. Keywords of the backend class (known_hosts= and hostkeypolicy= for paramiko, max_concurrency= and sftp_version= for asyncssh) go through the same call. backend= replaces all of them, and giving both is a TypeError.

Names are read from the server root: SFTPPath("data/x", host=h) is /data/x, not a file in the home directory of the account. A path joined from another keeps its backend, and so do parent, with_name, iterdir and relative_to.

Backends

A backend is the connections to one server and account, and the object paths share to share them:

Backend Library Notes
SFTPBackend paramiko one connection per thread; ProxyJump is not supported
AsyncSSHBackend asyncssh one connection per backend on a shared loop thread; recursive copy() and rm() run as concurrent requests (max_concurrency); hardlink_to() and chmod(follow_symlinks=False)

Without backend=, the class is chosen by SFTPPATH_BACKEND (auto, asyncssh or paramiko; auto prefers asyncssh). A backend is a context manager; close() ends every connection it holds and a later use connects again.

Host keys are verified. paramiko reads ~/.ssh/known_hosts and the UserKnownHostsFile of the ssh_config and rejects an unknown key (hostkeypolicy=, known_hosts=); asyncssh does the same (connect_opts=). Both raise SFTPHostKeyError and send no credential. Turning verification off (known_hosts=None) sends the password to whoever answers.

Errors

A failed request is the OSError pathlib raises for it, with errno and filename; a lost connection is ConnectionResetError, a timeout TimeoutError, a refused login SFTPAuthenticationError (a PermissionError) and an unknown or changed host key SFTPHostKeyError (a ConnectionError). Reading a directory is IsADirectoryError on every server; where the server opens a directory for reading, as OpenSSH does, open() succeeds and the first read raises it. Nothing of paramiko or asyncssh crosses the API. The library's own message is logged at DEBUG on the logger sftppath.

checksum() asks the server for the digest through the check-file-handle extension (OpenSSH does not have it); PathSyncer uses it and falls back to streaming when it raises NotImplementedError. symlink_to() and readlink() work on both backends.

Cost

A directory is listed with one request that carries every entry's attributes. walk(), glob() and rm(recursive=True) use them; iterdir() yields children without attributes, so is_dir() on each costs one request. Prefer walk() or glob() to a listing followed by a test of each child.