%there: SSH client

Jupyter magic commands

Commands are provided by the herethere.magic extension.

%load_ext herethere.magic

%connect-there

Connect to remote interpreter via SSH.

Command takes single optional argument: location of connection config.
If argument is not provided, values are loaded from the there.env file.

Config values could be overridden by environment variables with same names.

import os
os.environ["THERE_PORT"] = "8022"
%connect-there there.env

there.env example

# Hostname or address to connect to
THERE_HOST=127.0.0.1

# Port number to connect to
THERE_PORT=8023

# Credentials
THERE_USERNAME=debug
THERE_PASSWORD=xxx

%there group of commands

%there --help
Usage: there [OPTIONS] COMMAND [ARGS]...

  Group of commands to run on remote side.

Options:
  -b, --background           Run in background
  -l, --limit INTEGER RANGE  Number of lines to show when in background mode
  -d, --delay FLOAT          The time to wait in seconds before executing a
                             command

  --help                     Show this message and exit.

Commands:
  log     Listen for log records, send logging output to stdout.
  shell   Execute shell command on remote side.
  upload  Upload files and directories to `remotepath`.

Default action for %there, if command is not specified - execute python code.

there

Execute python code on the remote side.

%%there 
import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

shell

%there shell --help
Usage: there shell [OPTIONS]

  Execute shell command on remote side.

Options:
  --help  Show this message and exit.
%%there shell
for i in 1 2 3
do
    echo -n "$i"
done
123

Priodically run the top command in the background and show last two lines of output:

%%there -bl 2 shell
while :; do
    top -b | head -n 2
    sleep 10
done

upload

%there upload --help
Usage: there upload [OPTIONS] LOCALPATHS... REMOTEPATH

  Upload files and directories to `remotepath`.

Options:
  --help  Show this message and exit.

upload root directory is set by the HERE_CHROOT value of the here-server config.

!touch some.ico script.py
!mkdir -p dir1/dir2
%there upload some.ico script.py dir1 /
%%there shell
ls some.ico script.py
script.py
some.ico

log

%there log --help
Usage: there log [OPTIONS]

  Listen for log records, send logging output to stdout. This command blocks
  execution thread forever.

Options:
  --help  Show this message and exit.

Note

Since the command blocks and never ends, it is useful to run with –backgroud (-b) option

%there -b -l 10 log

Custom subcomands

New subcommands could be registered with the @there_code_shortcut decorator and click options:

from herethere.there.commands import there_code_shortcut
import click

@there_code_shortcut
@click.option("-n", "--number_to_print", type=int)
def mycommand(code: str, number_to_print):
        return f"print({number_to_print})"

%there mycommand -n 123
123

Using in the code

from herethere.everywhere import ConnectionConfig
from herethere.there import Client

config = ConnectionConfig.load(prefix="there")
client = Client()
await client.connect(config)
await client.runcode("print('Hello there :)')")
Hello there :)
await client.shell("sleep 1 ; ping -c 1 8.8.8.8")
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=37.4 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 37.477/37.477/37.477/0.000 ms
await client.disconnect()