Metadata-Version: 2.4
Name: hussh
Version: 0.2.1
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
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: Topic :: System :: Shells
Classifier: Topic :: System :: Networking
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Operating System :: POSIX
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS :: MacOS X
Requires-Dist: docker ; extra == 'dev'
Requires-Dist: maturin ; extra == 'dev'
Requires-Dist: patchelf ; sys_platform == 'linux' and extra == 'dev'
Requires-Dist: pexpect ; extra == 'dev'
Requires-Dist: pre-commit ; extra == 'dev'
Requires-Dist: pytest ; extra == 'dev'
Requires-Dist: pytest-asyncio ; extra == 'dev'
Requires-Dist: pytest-randomly ; extra == 'dev'
Requires-Dist: ruff ; extra == 'dev'
Requires-Dist: tox ; extra == 'dev'
Requires-Dist: tox-uv ; extra == 'dev'
Requires-Dist: docker ; extra == 'test'
Requires-Dist: pexpect ; extra == 'test'
Requires-Dist: pytest ; extra == 'test'
Requires-Dist: pytest-asyncio ; extra == 'test'
Requires-Dist: pytest-randomly ; extra == 'test'
Provides-Extra: dev
Provides-Extra: test
License-File: LICENSE
Summary: SSH for Humans
Keywords: ssh,ssh2,rust,pyo3
Author-email: Jacob J Callahan <jacob.callahan05@gmail.com>
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# Hussh: SSH for humans.
[![image](https://img.shields.io/pypi/v/hussh.svg)](https://pypi.python.org/pypi/hussh)
[![image](https://img.shields.io/pypi/pyversions/hussh.svg)](https://pypi.python.org/pypi/hussh)
![PyPI - Wheel](https://img.shields.io/pypi/wheel/hussh)
[![Actions status](https://github.com/jacobcallahan/hussh/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/jacobcallahan/hussh/actions)

Hussh (pronounced "hush") is a client-side ssh library that offers low level performance through a high level interface.

Hussh uses [pyo3](https://docs.rs/pyo3/latest/pyo3/) to create Python bindings around the [ssh2](https://docs.rs/ssh2/latest/ssh2/) library for Rust.

# Installation
```
pip install hussh
```

# QuickStart
Hussh currently just offers a `Connection` class as your primary interface.
```python
from hussh import Connection

conn = Connection(host="my.test.server", username="user", password="pass")
result = conn.execute("ls")
print(result.stdout)
```

That's it! One import and class instantion is all you need to:
- Execute commands
- Perform SCP actions
- Perform SFTP actions
- Get an interactive shell

# Why Hussh?
- 🔥 Blazingly fast!
- 🪶 Incredibly lightweight!
- 🧠 Super easy to use!

## Benchmarks
Hussh demonstrates the performance you'd expect from a low level ssh library.
Hussh is also much lighter weight in both total memory and memory allocations.

Local Server
![Local Server Benchmarks](benchmarks/local_server_bench.png)

Remote Server
![Remote Server Benchmarks](benchmarks/remote_server_bench.png)

### Try it for yourself!
Hussh's benchmark script are also open sourced in the `benchmarks` directory in this repository.
Clone the repo, follow the setup instructions, then let us know how it did!

# Authentication
You've already seen password-based authentication, but here it is again.
```python
conn = Connection(host="my.test.server", username="user", password="pass")

#  or leave out username and connect as root
conn = Connection(host="my.test.server", password="pass")
```

If you prefer key-based authentication, Hussh can do that as well.
```python
conn = Connection(host="my.test.server", private_key="~/.ssh/id_rsa")

# If your key is password protected, just use the password argument
conn = Connection(host="my.test.server", private_key="~/.ssh/id_rsa", password="pass")
```

Hussh can also do agent-based authentication, if you've already established it.
```python
conn = Connection("my.test.server")
```

## Cleaning up after yourself

Hussh will clean up after itself automatically when the `Connection` object is garbage collected.

However, if you want to more explicitly clean up after yourself, you can `close` the connection.
```python
conn.close()
```
or you can use the `Connection` class' context manager, which will `close` when you exit the context.
```python
with Connection(host="my.test.server", password="pass") as conn:
   result = conn.execute("ls")
assert result.status == 0
```

# Executing commands
The most basic foundation of ssh libraries is the ability to execute commands against the remote host.
For Hussh, just use the `Connection` object's `execute` method.
```python
result = conn.execute("whoami")
print(result.stdout, result.stderr, result.status)
```
Each execute returns an `SSHResult` object with command's stdout, stderr, and status.

# SFTP
If you need to transfer files to/from the remote host, SFTP may be your best bet.

## Writing files and data
```python
# write a local file to the remote destination
conn.sftp_write(local_path="/path/to/my/file", remote_path="/dest/path/file")

# Write UTF-8 data to a remote file
conn.sftp_write_data(data="Hello there!", remote_path="/dest/path/file")
```

## Reading files
```python
# You can copy a remote file to a local destination
conn.sftp_read(remote_path="/dest/path/file", local_path="/path/to/my/file")
# Or copy the remote file contents to a string
contents = conn.sftp_read(remote_path="/dest/path/file")
```

## Copy files from one connection to another
Hussh offers a shortcut that allows you to copy a file between two established connections.
```python
source_conn = Connection("my.first.server")
dest_conn = Connection("my.second.server", password="secret")
# Copy from source to destination
source_conn.remote_copy(source_path="/root/myfile.txt", dest_conn=dest_conn)
```
By default, if you don't pass in an alternate `dest_path`, Hussh will copy it to the same path as it came from on source.


# SCP
For remote servers that support SCP, Hussh can do that to.

## Writing files and data
```python
# write a local file to the remote destination
conn.scp_write(local_path="/path/to/my/file", remote_path="/dest/path/file")

# Write UTF-8 data to a remote file
conn.scp_write_data(data="Hello there!", remote_path="/dest/path/file")
```

## Reading files
```python
# You can copy a remote file to a local destination
conn.scp_read(remote_path="/dest/path/file", local_path="/path/to/my/file")
# Or copy the remote file contents to a string
contents = conn.scp_read(remote_path="/dest/path/file")
```

# Tailing Files
Hussh offers a built-in method for tailing files on a `Connection` with the `tail` method.
```python
with conn.tail("/path/to/file.txt") as tf:
   # perform some actions or wait
   print(tf.read())  # at any time, you can read any unread contents
   # when you're done tailing, exit the context manager
print(tf.contents)
```

# Interactive Shell
If you need to keep a shell open to perform more complex interactions, you can get an `InteractiveShell` instance from the `Connection` class instance.
To use the interactive shell, it is recommended to use the `shell()` context manager from the `Connection` class.
You can send commands to the shell using the `send` method, then get the results from `result` when you exit the context manager.

```python
with conn.shell() as shell:
   shell.send("ls")
   shell.send("pwd")
   shell.send("whoami")

print(shell.result.stdout)
```
**Note:** The `read` method sends an EOF to the shell, so you won't be able to send more commands after calling `read`. If you want to send more commands, you would need to create a new `InteractiveShell` instance.

# Asynchronous Usage
Hussh also offers an `AsyncConnection` class for asynchronous operations.

## QuickStart
```python
import asyncio
from hussh.aio import AsyncConnection

async def main():
    async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
        result = await conn.execute("ls")
    print(result.stdout)

asyncio.run(main())
```

## Timeouts
You can specify a timeout (in seconds) for the connection and for individual command executions.

```python
# Set a default timeout for the connection
async with AsyncConnection(host="my.test.server", timeout=10) as conn:
    # This command will use the connection's default timeout (10s)
    await conn.execute("sleep 5")
    
    # You can override the timeout for specific commands
    try:
        await conn.execute("sleep 20", timeout=5)
    except TimeoutError:
        print("Command timed out!")
```

## SFTP
Async SFTP operations are available directly on the connection, mirroring the synchronous API.
```python
async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
    # Write a local file to the remote server
    await conn.sftp_write(local_path="/path/to/my/file", remote_path="/dest/path/file")

    # Write UTF-8 data to a remote file
    await conn.sftp_write_data(data="Hello there!", remote_path="/dest/path/file")

    # Read a remote file to a local destination
    await conn.sftp_read(remote_path="/dest/path/file", local_path="/path/to/my/file")

    # Read a remote file's contents as a string
    contents = await conn.sftp_read(remote_path="/dest/path/file")

    # List directory contents
    files = await conn.sftp_list("/remote/path")
```

## Interactive Shell
```python
async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
    async with await conn.shell() as shell:
        await shell.send("ls")
        result = await shell.read()
        print(result.stdout)
        # one of the advantages of an async shell is that you can read and write multiple times
        await shell.send("whoami")
        result = await shell.read()
        print(f"I'm logged in as {result.stdout}")
```

## Tailing Files
```python
async with AsyncConnection(host="my.test.server", username="user", password="pass") as conn:
    async with conn.tail("/path/to/file.txt") as tf:
        print(await tf.read())
    # You can also access the full contents read during the session
    print(tf.contents)
```

# Disclaimer
This is an early project that should not be used in sensitive production code!
There isn't full exception handling, so expect some Rust panics to fall through.
With that said, try it out and let me know your thoughts!

# Future Features
- Concurrent actions class
- Low level bindings
- Misc codebase improvements
- TBD...

