Metadata-Version: 2.4
Name: scrippy-remote
Version: 4.0.0
Summary: "SSH, SFTP, FTP and CIFS client for the Scrippy framework."
Home-page: https://codeberg.org/scrippy/scrippy-remote
Author: Michael Costa, Florent Chevalier
Author-email: michael.costa@mcos.nc, florent.chevalier.nc@gmail.com
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Environment :: Console
Classifier: Development Status :: 5 - Production/Stable
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: paramiko>=5.0.0
Requires-Dist: pysmb>=1.2.7
Provides-Extra: dev
Requires-Dist: twine; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: wheel>=0.37.0; extra == "dev"
Requires-Dist: flake8>=4.0.1; extra == "dev"
Requires-Dist: flake8-breakpoint>=1.1.0; extra == "dev"
Requires-Dist: flake8-builtins>=1.5.3; extra == "dev"
Requires-Dist: flake8-print>=4.0.0; extra == "dev"
Requires-Dist: flake8-return>=1.1.3; extra == "dev"
Requires-Dist: pep8-naming>=0.8.2; extra == "dev"
Requires-Dist: pytest>=6.2.2; extra == "dev"
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
Requires-Dist: bump2version>=1.0.1; extra == "dev"
Requires-Dist: setuptools>=60.9.2; extra == "dev"
Requires-Dist: pypirc-chappers>=1.0.8; extra == "dev"
Requires-Dist: pylint>=2.12.2; extra == "dev"
Dynamic: license-file


![Build Status](https://drone-ext.mcos.nc/api/badges/scrippy/scrippy-remote/status.svg) ![License](https://img.shields.io/static/v1?label=license&color=orange&message=MIT) ![Language](https://img.shields.io/static/v1?label=language&color=informational&message=Python)

![Scrippy, my scrangourou friend](./scrippy-remote.png "Scrippy, my scrangourou friend")

# `scrippy_remote`

SSH/SFTP, FTP/FTPS and CIFS client for the [`Scrippy`](https://codeberg.org/scrippy) framework.

## Prerequisites

- Python >= 3.11
- The following modules, automatically installed:
  - paramiko
  - pysmb

## Installation

### With `pip`

```bash
pip install scrippy-remote
```

### From source

```bash
git clone https://codeberg.org/scrippy/scrippy-remote.git
cd scrippy-remote
make install
```

## Usage

`scrippy_remote` provides one class per protocol:

| Class | Module | Protocol |
|---|---|---|
| `Ssh` | `scrippy_remote.ssh` | Remote command execution and file transfer over SSH/SFTP |
| `Ftp` | `scrippy_remote.ftp` | File transfer over FTP, FTPS (implicit TLS) or FTPES (explicit TLS) |
| `Cifs` | `scrippy_remote.cifs` | File transfer within a CIFS/Samba shared folder (limited support) |

Each class is used as a context manager: the connection is opened when entering the `with` block and closed when leaving it.

The three classes share the same methods:

| Method | Description |
|---|---|
| `get_file(remote_file, local_dir)` | Retrieve a remote file |
| `put_file(local_file, remote_dir)` | Transfer a file to the remote host |
| `local_mirror(remote_dir, local_dir)` | Copy a remote directory tree to the local host |
| `remote_mirror(local_dir, remote_dir)` | Copy a local directory tree to the remote host |
| `list_remote_dir(remote_dir)` | List the files (or directories) of a remote directory |
| `create_remote_dir(remote_dir)` | Create a remote directory |
| `delete_remote_file(remote_file)` | Delete a remote file |
| `delete_remote_dir(remote_dir)` | Delete a remote directory |
| `list_local_dir()`, `create_local_dir()`, `delete_local_file()`, `delete_local_dir()` | [Local operations](README/local.md) |

In addition, `Ssh` provides `exec()` to execute commands, and `Cifs` provides `open()` to read or write a remote file.

Example: copy a file from a FTP server to a SSH server, the local host acting as a buffer:

```python
from scrippy_remote.ftp import Ftp
from scrippy_remote.ssh import Ssh

with Ftp(host="ftp.flying.circus",
         username="luiggi.vercotti",
         password="dead_parrot") as ftp:
  ftp.get_file(remote_file="sketches/parrot.txt",
               local_dir="/tmp")

with Ssh(username="luiggi.vercotti",
         host="srv.flying.circus",
         key="/home/luiggi.vercotti/.ssh/id_rsa") as ssh:
  ssh.put_file(local_file="/tmp/parrot.txt",
               remote_dir="/home/luiggi.vercotti/sketches",
               create_dirs=True)
  ssh.exec(command="chmod 640 /home/luiggi.vercotti/sketches/parrot.txt")
```

### Error handling

All errors raise a `scrippy_remote.ScrippyRemoteError`. Its message starts with the error category, e.g. `[SshTransferError]`. The categories are listed in each protocol documentation.

Most methods accept an `exit_on_error` argument (default: `True`). When set to `False`, errors are only logged as warnings and the method goes on (e.g. a mirror skips the faulty file). Connection and argument errors are always raised.

```python
from scrippy_remote import ScrippyRemoteError, logger
from scrippy_remote.ssh import Ssh

try:
  with Ssh(username="luiggi.vercotti",
           host="srv.flying.circus",
           key="/home/luiggi.vercotti/.ssh/id_rsa") as ssh:
    ssh.delete_remote_dir(remote_dir="/home/luiggi.vercotti/dead", recursive=True)
except ScrippyRemoteError as err:
  logger.critical(f"{err}")
```

### Documentation

- [SSH/SFTP operations](README/ssh.md):
  - [connection](README/ssh.md#connection)
  - [errors](README/ssh.md#error-handling)
  - [command execution](README/ssh.md#execute-a-command-on-a-remote-host)
  - [file transfers](README/ssh.md#retrieve-a-remote-file)
  - [mirrors](README/ssh.md#mirror-a-remote-directory-to-a-local-directory)
  - [remote directories](README/ssh.md#list-remote-directory-content)
- [FTP operations](README/ftp.md):
  - [connection](README/ftp.md#connection)
  - [errors](README/ftp.md#error-handling)
  - [file transfers](README/ftp.md#retrieve-a-remote-file)
  - [mirrors](README/ftp.md#mirror-a-remote-directory-to-a-local-directory)
  - [remote directories](README/ftp.md#list-remote-directory-content)
- [CIFS operations](README/cifs.md):
  - [connection](README/cifs.md#connection)
  - [errors](README/cifs.md#error-handling)
  - [file transfers](README/cifs.md#retrieve-a-remote-file)
  - [mirrors](README/cifs.md#mirror-a-remote-directory-to-a-local-directory)
  - [remote directories](README/cifs.md#list-remote-directory-content)
  - [remote files](README/cifs.md#open-a-file-in-write-mode)
- [Local operations](README/local.md):
  - [listing](README/local.md#list-a-local-directory-content-with-a-pattern-filter)
  - [creation](README/local.md#create-a-local-directory)
  - [deletion](README/local.md#delete-a-local-directory-recursively)

The docstrings in the source code remain the reference documentation. `make doc` generates the class and package diagrams (`classes_scrippy-remote.svg`, `packages_scrippy-remote.svg`).

## Tests

The tests need the test servers (SSH, FTP and Samba) provided as containers in `tests/container`:

```bash
make -C tests/container build run
python3 -m pip install .[dev]
make tests
```

The tests reach the servers through the `sshd`, `vsftpd` and `samba` host names, which must be resolvable (e.g. run the tests from a container attached to the `container_default` network).
