Terminal Sessions

Vty runs a command on the remote host and streams its output. A session with a terminal gives job control, a window size and full screen programs. A session with pipes keeps the bytes exactly and reports a real end of input.

The module needs a POSIX host, because a terminal comes from pty and termios.

The output call is a streaming call, so the bytes arrive as the child writes them. See interactive shell for the command line client built on this tool, or SSH multiplexing for ordinary OpenSSH clients.

class rmote.tools.vty.Vty[source]

Bases: Tool

Start a command on the remote host and stream its output.

Open a session, read its output, write to it, and close it. The output call is a streaming call, so the bytes arrive as the child writes them:

key = await remote(Vty.open, ["/bin/sh"], want_pty=False)
async for chunk in remote(Vty.output, key):
    handle(chunk)
status = await remote(Vty.close, key)

Close releases the terminal and the child on every path, so a caller must call it even after a failure.

A session with pipes works on every platform. A session with a terminal needs a POSIX host for now.

CHUNK_SIZE = 65536
async static close(key)[source]

Release the session and return the exit status of its child.

Repeated calls are safe. A session that already ended reports its status again.

Return type:

getint

async static end_input(key)[source]

Tell the child that its input ended, and keep reading its output.

Return type:

None

last_key = 0
async static open(argv=None, rows=24, cols=80, term='', env=None, cwd=None, want_pty=True, launcher='', separate_stderr=False)[source]

Start a command and return the key of its session.

Parameters:
  • argv (list[Text] | None) – Command and arguments. None or an empty list starts the command processor of the remote host.

  • rows (getint) – Terminal height in character cells.

  • cols (getint) – Terminal width in character cells.

  • term (Text) – Value of the TERM variable. An empty value keeps the environment of the remote interpreter.

  • env (WSGIEnvironment[Text, Text] | None) – Extra environment variables for the child.

  • cwd (Text | None) – Working directory of the child. None keeps the remote default.

  • want_pty (bool) – True gives the child a terminal, which needs a POSIX host. False gives it pipes, which work everywhere.

  • launcher (Text) – Source of the helper that takes the controlling terminal. The caller produces it with launcher_source(), because a transferred module has no file of its own. An empty value makes this side produce it, which works only with the file present.

  • separate_stderr (bool) – Keep stderr separate in pipe mode. Read it with stderr() concurrently with output() to avoid blocking the child. A terminal always combines stdout and stderr.

Return type:

getint

Returns:

The key of the session, for the other methods.

Raises:
static output(key)[source]

Yield the output of the session until it ends.

The stream ends when the child releases its output. The exit status comes from close().

Parameters:

key (getint) – Key that open() returned.

Yields:

Chunks of output, in the order the child wrote them.

async static resize(key, rows, cols)[source]

Report a new window size to the terminal of the session.

Return type:

None

sessions: dict[int, Session] = {}
async static shell(command='', *, rows=24, cols=80, term='', env=None, want_pty=True, launcher='')[source]

Run a command through the remote user’s shell, or open a login shell.

Pipe sessions keep stderr separate; read stderr() alongside output(). All other arguments have the same meaning as in open().

Return type:

getint

static stderr(key)[source]

Yield separate stderr until EOF; empty for terminals or merged pipes.

Return type:

AsyncIterator[bytes]

async static wait(key)[source]

Wait for the child and return its exit status.

Return type:

getint

async static write(key, data)[source]

Pass payload bytes to the child.

Return type:

None

Session backends

The tool chooses a backend from the want_pty argument. Application code uses Vty and does not touch these classes, but their contracts explain how the end of input and the cleanup behave.

class rmote.tools.vty.Session[source]

Bases: object

A child process on the remote host, with the descriptors it needs.

The contract holds no process object of its own, because a backend can run the child through an interface that gives none.

async end_input()[source]

Tell the child that its input ended.

Return type:

None

async read(size)[source]

Read up to size bytes of output. Empty bytes mean the end.

Return type:

bytes

async read_stderr(size)[source]

Read separate stderr, or EOF when it is merged into output.

Return type:

bytes

async release()[source]

End the child and release the descriptors. Return when it is done.

Return type:

None

resize(rows, cols)[source]

Apply a new window size. A session without a terminal has none.

Return type:

None

property status: int | None

Exit status of the child, or None while it runs.

async wait()[source]

Wait for the child and return its exit status.

Return type:

getint

async write(data)[source]

Pass payload bytes to the child.

Return type:

None

class rmote.tools.vty.TerminalSession(process, master_fd, slave_fd)[source]

Bases: ProcessSession

A child process behind a pseudo terminal.

close_slave()[source]
Return type:

None

async end_input()[source]

Write the VEOF character of the terminal.

A terminal has no half close. A reader in canonical mode treats VEOF as the end of its input. A program that set raw mode gets the character as ordinary data, so this is not a general half close.

Return type:

None

end_of_input_character()[source]

Return the VEOF character of the terminal, or Ctrl-D.

Return type:

bytes

async read(size)[source]

Read up to size bytes of output. Empty bytes mean the end.

Return type:

bytes

async release()[source]

End the child and release the descriptors. Return when it is done.

Return type:

None

resize(rows, cols)[source]

Apply a new window size. A session without a terminal has none.

Return type:

None

async classmethod start(command, rows, cols, child_env, cwd, launcher)[source]

Open a terminal and start the child on its slave side.

The master descriptor is stored before anything can fail, so the session releases it on every path.

Return type:

Session

async write(data)[source]

Pass payload bytes to the child.

Return type:

None

class rmote.tools.vty.PipeSession(process)[source]

Bases: ProcessSession

A child process with ordinary pipes and no terminal.

A pipe has a real half close, so the end of input reaches the child exactly. The bytes pass through without terminal processing, which keeps binary data and line endings as they are. This backend works on every platform.

async end_input()[source]

Close the write end of the child’s input. This is a real half close.

Return type:

None

async read(size)[source]

Read up to size bytes of output. Empty bytes mean the end.

Return type:

bytes

async read_stderr(size)[source]

Read separate stderr, or EOF when it is merged into output.

Return type:

bytes

async release()[source]

End the child and release the descriptors. Return when it is done.

Return type:

None

async write(data)[source]

Pass payload bytes to the child.

Return type:

None

Controlling terminal

A child needs a controlling terminal, or a shell reports that job control is off. The helper below takes it after exec, in a fresh interpreter with one thread. A preexec_fn would instead run Python between fork and exec in a process that has threads, where the standard library warns about a deadlock.

rmote.tools.vty.take_controlling_terminal(motd_paths=())[source]

Take the controlling terminal, then run the requested command.

The function runs as a fresh interpreter after exec, so the process has one thread and Python is safe to use. A preexec_fn would instead run Python between fork and exec in a process that has threads, where the standard library warns about a deadlock.

Descriptor 0 is the terminal, and the process is already a session leader. Without TIOCSCTTY a shell reports that job control is off.

Return type:

None

rmote.tools.vty.launcher_source(*, motd_paths=())[source]

Return the launcher as source that python -c can run.

The launcher has to be a fresh interpreter, so it travels as text. The text comes from the function itself, which keeps one definition that the linter and the type checker also read. A transferred module has no file, so only the side that holds the file can produce the text.

The child displays files from motd_paths before exec. Callers must supply paths only for interactive login shells.

Return type:

Text

Helpers

rmote.tools.vty.default_shell()[source]

Return the login shell of the current user, or /bin/sh.

Return type:

Text

rmote.tools.vty.set_winsize(fd, rows, cols)[source]

Set the window size of a terminal descriptor.

Return type:

None

async rmote.tools.vty.wait_readable(fd)[source]

Wait until fd has bytes to read.

Return type:

None

async rmote.tools.vty.wait_writable(fd)[source]

Wait until fd accepts more bytes.

Return type:

None

async rmote.tools.vty.write_all(fd, data)[source]

Write every byte of data.

A terminal or a pipe can be full, and one write can be partial. The function waits for writability instead of a timer, so the event loop stays free.

Return type:

None