Skip to content

feat: Progress Callbacks for File Transfers #85

Description

@JacobCallahan

Summary

Expose real-time transfer progress to Python by adding an optional callback parameter to SFTP and SCP read/write methods, enabling integration with progress-bar libraries such as tqdm or rich.

Motivation

For large file transfers there is currently no way to observe progress. Users are left guessing whether the operation is still running, making hussh difficult to use in interactive scripts and deployment tools.

Proposed API

from tqdm import tqdm

with Connection("myserver.example.com", username="deploy", password="...") as conn:
    with tqdm(unit="B", unit_scale=True, desc="upload") as bar:
        def on_progress(transferred: int, total: int) -> None:
            bar.total = total
            bar.update(transferred - bar.n)

        conn.sftp_write("/local/large_file.tar.gz", "/remote/large_file.tar.gz",
                        callback=on_progress)

The callback signature is:

Callable[[int, int], None]  # (bytes_transferred, total_bytes)

The same callback parameter should be available on:

Method Direction
sftp_write / sftp_write_file local → remote
sftp_read remote → local
scp_write local → remote
scp_read remote → local
put_dir / get_dir (see #84) aggregate bytes across all files

Implementation Notes

  • On the Rust side, buffer SFTP/SCP reads and writes in chunks (e.g. 32 KiB). After each chunk, invoke a cached Python callable via PyO3's call1 / call_bound with (transferred, total).
  • For SCP, the total size is known from the SCP protocol header before data transfer begins. For SFTP, stat the file first to obtain size.
  • The callback must release the GIL between chunks (Python::allow_threads) to avoid blocking Rust I/O while Python executes the callback.
  • callback=None (the default) should have zero overhead—skip the PyO3 call entirely.
  • For async transfers, invoke the callback as a coroutine if it is a coroutine function, otherwise call it synchronously.

Acceptance Criteria

  • callback parameter on all four SFTP and SCP read/write methods
  • Callback receives (transferred_bytes: int, total_bytes: int)
  • Default callback=None introduces no measurable overhead
  • Async methods accept both plain callables and coroutine functions
  • Tests verify callback is called with correct byte accounting
  • Documentation includes a tqdm integration example

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions