Metadata-Version: 2.5
Name: sftppath
Version: 0.1.1
Summary: pathlib-style paths for SFTP servers, on paramiko or asyncssh, and the sftp: scheme for uripath
Project-URL: Homepage, https://github.com/jose-pr/sftppath/
Project-URL: Documentation, https://jose-pr.github.io/sftppath/
Project-URL: Issues, https://github.com/jose-pr/sftppath/issues
Author: Jose A
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: pathapi<0.2,>=0.1.0
Provides-Extra: all
Requires-Dist: asyncssh<2.22,>=2.20.0; (python_version < '3.10') and extra == 'all'
Requires-Dist: asyncssh<3,>=2.20.0; (python_version >= '3.10') and extra == 'all'
Requires-Dist: paramiko<6,>=5.0; extra == 'all'
Requires-Dist: pathapi-uri<0.2,>=0.1.0; extra == 'all'
Provides-Extra: asyncssh
Requires-Dist: asyncssh<2.22,>=2.20.0; (python_version < '3.10') and extra == 'asyncssh'
Requires-Dist: asyncssh<3,>=2.20.0; (python_version >= '3.10') and extra == 'asyncssh'
Provides-Extra: dev
Requires-Dist: black; extra == 'dev'
Requires-Dist: build; extra == 'dev'
Requires-Dist: hatchling; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs; extra == 'docs'
Requires-Dist: mkdocs-material; extra == 'docs'
Requires-Dist: mkdocstrings[python]; extra == 'docs'
Provides-Extra: paramiko
Requires-Dist: paramiko<6,>=5.0; extra == 'paramiko'
Provides-Extra: uri
Requires-Dist: pathapi-uri<0.2,>=0.1.0; extra == 'uri'
Description-Content-Type: text/markdown

# sftppath

[![Version](https://img.shields.io/pypi/v/sftppath.svg)](https://pypi.org/project/sftppath/)
[![Python versions](https://img.shields.io/pypi/pyversions/sftppath.svg)](https://pypi.org/project/sftppath/)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jose-pr/sftppath/blob/main/LICENSE)
[![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://jose-pr.github.io/sftppath/)
[![CI](https://img.shields.io/github/actions/workflow/status/jose-pr/sftppath/test.yml)](https://github.com/jose-pr/sftppath/actions/workflows/test.yml)

**`pathlib` for SFTP servers.** `SFTPPath("/srv/data/report.txt", host="files.example.org")`
has the method names, signatures and exception types of `pathlib.Path`, over
paramiko or asyncssh, and needs no URI. It builds on
[`pathapi`](https://github.com/jose-pr/pathapi) alone; with
[`uripath`](https://github.com/jose-pr/uripath) installed it also provides the
`sftp:` scheme. It is documented at <https://jose-pr.github.io/sftppath/>.

## Features

- **A `pathlib` path on a server**: `open`, `read_text`, `iterdir`, `glob`,
  `stat`, `mkdir`, `rename`, `replace`, `chmod`, `symlink_to`, `readlink` and
  the rest, plus `walk`, `copy`, `move`, `rm` and `PathSyncer` from
  `pathapi`. Nothing connects until the first request.
- **Two backends, one behaviour**: `SFTPBackend` (paramiko) and `AsyncSSHBackend`
  (asyncssh) raise the same exceptions for the same server answers; the choice is
  a keyword, a subclass attribute or `SFTPPATH_BACKEND`.
- **Native tree operations**: on asyncssh a recursive `copy()` and `rm()` run as
  concurrent requests on one connection, bounded by `max_concurrency`.
- **ssh_config**: `HostName`, `Port`, `User`, `IdentityFile`, `ProxyCommand` and
  `Include` apply to the host you name; host keys are verified.
- **Server-side checksums**: `checksum()` uses the `check-file-handle`
  extension where the server has it, and `PathSyncer` falls back to streaming
  where it does not.
- **No SSH library at import**: `import sftppath` loads neither library nor
  `uripath`. Errors are `OSError` subclasses, `SFTPAuthenticationError` and
  `SFTPHostKeyError`; no library exception crosses the API.
- **The `sftp:` scheme** for `uripath`: `URIPath("sftp://user@host/dir")` is an
  `SFTPURIPath` that delegates to an `SFTPPath`.

## Installation

```bash
pip install "sftppath[paramiko]"
```

Requires `pathapi`, which pip installs with it, and Python 3.9 or newer.

| Install | Adds |
| --- | --- |
| `pip install sftppath` | the path and the backend contract; no SSH library |
| `pip install "sftppath[paramiko]"` | the paramiko backend (`paramiko>=5.0,<6`) |
| `pip install "sftppath[asyncssh]"` | the asyncssh backend and the native tree operations (`asyncssh>=2.20`) |
| `pip install "sftppath[uri]"` | `uripath` and with it the `sftp:` scheme |
| `pip install "sftppath[all]"` | the three above |

## Quick start

**A path on a server**: the endpoint goes in as keywords; the user, password and
port may come from your `~/.ssh/config` instead:

<!-- example: skip: connects to a server -->
```python
from sftppath import SFTPPath

report = SFTPPath("/srv/data/report.txt", host="files.example.org", user="deploy")
print(report.read_text())
for child in report.parent.iterdir():
    print(child.name, child.stat().st_size)
```

**Share a connection**: a backend is the connections to one server and
account, and a context manager:

<!-- example: skip: connects to a server -->
```python
from sftppath import SFTPBackend, SFTPPath

with SFTPBackend("files.example.org", user="deploy", timeout=10) as backend:
    root = SFTPPath("/srv/data", backend=backend)
    (root / "out").mkdir(exist_ok=True)
    (root / "out" / "n.txt").write_text("1")
    root.copy(SFTPPath("/srv/backup", backend=backend), recursive=True)
```

**Mirror a tree** with `PathSyncer`, onto a local directory:

<!-- example: skip: connects to a server -->
```python
from pathapi import LocalPath
from pathapi.sync import PathSyncer
from sftppath import SFTPPath

remote = SFTPPath("/var/log", host="files.example.org", user="deploy")
PathSyncer(remove_missing=True).sync(remote, LocalPath("mirror"))
```

**The scheme**, with `uripath` installed:

<!-- example: skip: connects to a server -->
```python
from uripath import URIPath

print(URIPath("sftp://deploy@files.example.org/srv/data/report.txt").read_text())
```

## API overview

| Module | Purpose |
| --- | --- |
| `sftppath` | `SFTPPath`, the backends `SFTPBackend`, `AsyncSSHBackend`, `BaseSFTPBackend` and `CheckFileSFTPBackend`, `SFTPEndpoint`, `DEFAULT_PORT`, `DEFAULT_SSH_CONFIG`, the errors and `__version__` |
| `sftppath.exceptions` | `SFTPPathError`, `SFTPAuthenticationError` and `SFTPHostKeyError` |
| `sftppath.uri` | `SFTPURIPath` and `SFTPURIBackend`, the `sftp:` scheme of `uripath` (needs the `uri` extra) |

The full reference is on the [documentation site](https://jose-pr.github.io/sftppath/);
an agent-oriented API summary ships inside the package as `sftppath/AGENTS.md`.

## Development

```bash
pip install -e ".[dev,all]"
python -m pytest -q -rs
```

The tests start SSH servers of their own on the loopback interface with
throwaway keys; they never reach another host. A test for a library or an extra
that is not installed is skipped, so check the skip list (`-rs`). Contributor
notes (virtual environments, formatting, CI, releases) are in the repository's
[`AGENTS.md`](https://github.com/jose-pr/sftppath/blob/main/AGENTS.md). To
preview the documentation: `pip install -e ".[all,docs]"` then `mkdocs serve`.

Changes are recorded in the
[changelog](https://github.com/jose-pr/sftppath/blob/main/CHANGELOG.md).

### Releasing

This project follows [Semantic Versioning](https://semver.org/) and keeps a
[`CHANGELOG.md`](https://github.com/jose-pr/sftppath/blob/main/CHANGELOG.md). Pushing a tag matching `v*` triggers the release
workflow: test gate → build (checking the tag names the version built) → a strict
docs build as a gate → GitHub release → publish. The release workflow never deploys
the docs site itself: for a final release its last job dispatches the docs workflow
at the tag, which owns every Pages deploy.

## License

MIT — see [LICENSE](https://github.com/jose-pr/sftppath/blob/main/LICENSE).
