host.session

Persistent shell sessions for remote command execution.

ShellSession provides a unified, stateful command execution layer on top of SSH and telnet connections. All commands share a single shell — state (working directory, environment, user context) persists between calls.

Key features: - Sentinel-based output demarcation with exit code extraction - Expect-enhanced run_cmd for interactive commands (sudo, su, etc.) - Raw send/expect for driving non-shell interactive programs - Per-command timeout with Ctrl+C recovery

class otto.host.session.ShellSession

Bases: ABC

Abstract base for persistent shell sessions.

Subclasses implement the I/O primitives (_write, _read_until_pattern, _open, close). The base class provides shared logic for sentinel-wrapped command execution, expect handling, and timeout recovery.

property alive : bool

Whether the session is initialized and responsive.

abstract async close()

Close the session and release resources.

Return type:

None

async send(text)

Send raw text to the session’s stdin.

Use this for driving interactive programs (REPLs, custom CLIs). The caller is responsible for including line endings.

Return type:

None

async expect(pattern, timeout=30.0)

Wait for a pattern in the output stream.

Returns captured data up to and including the match. Raises asyncio.TimeoutError if the pattern isn’t seen within timeout. Marks the session as dead if EOF is received.

Return type:

str

async run_cmd(cmd, expects=None, timeout=None)

Execute a shell command with sentinel-based output demarcation.

Output is streamed line-by-line to _on_output as it arrives. Sentinels and echoed command text are filtered out automatically.

Parameters:
  • cmd – The shell command to execute.

  • expects – Optional list of (pattern, response) tuples. If a pattern appears in the output before the end sentinel, the response is automatically sent. Expects are inherently optional — if the pattern never appears, the end sentinel matches normally.

  • timeout – Optional timeout in seconds. On expiry, the session attempts recovery via Ctrl+C and returns Status.Error.

Return type:

CommandStatus

Returns:

CommandStatus with exit code extracted from the sentinel.

class otto.host.session.SshSession(conn)

Bases: ShellSession

SSH persistent shell session via asyncssh create_process().

async close()

Close the session and release resources.

Return type:

None

class otto.host.session.TelnetSession(reader, writer, _owned_client=None)

Bases: ShellSession

Telnet persistent shell session via telnetlib3 streams.

async close()

Close the session and release resources.

Return type:

None

class otto.host.session.LocalSession

Bases: ShellSession

Local persistent bash shell session via asyncio subprocess.

Implements the ShellSession I/O primitives using a long-running bash process, giving LocalHost the same sentinel-wrapped execution, expect handling, and timeout recovery that remote sessions enjoy.

async close()

Close the session and release resources.

Return type:

None

class otto.host.session.HostSession(name, session, log_command, log_output, deregister)

Bases: object

A named persistent shell session on any host type.

Obtained via await host.open_session(name). Supports the async context manager protocol for automatic cleanup.

Example:

async with (await host.open_session("monitor")) as mon:
    result = await mon.run("stat /tmp/file.bin")

Or without a context manager:

mon = await host.open_session("monitor")
try:
    result = await mon.run("stat /tmp/file.bin")
finally:
    await mon.close()
property alive : bool

Whether the underlying shell session is still active.

async run(cmds, expects=None, timeout=10.0)

Execute one or more commands on this named session.

Mirrors Host.run(): accepts a str, a ShellCommand, or a sequence mixing the two, and always returns a RunResult. Per-command expects / timeout on a ShellCommand override the run-level defaults; a scalar Expect tuple at the run level is normalized to a one-element list.

Return type:

RunResult

async send(text)

Send raw text to this session’s stdin. See RemoteHost.send().

Return type:

None

async expect(pattern, timeout=10.0)

Wait for a pattern in this session’s output. See RemoteHost.expect().

Return type:

str

async close()

Close this session and remove it from the host’s session registry.

Return type:

None

class otto.host.session.SessionManager(connections=None, name='', log_command=<function SessionManager.<lambda>>, log_output=<function SessionManager.<lambda>>, session_factory=None, oneshot_factory=None)

Bases: object

Manages persistent shell sessions for any host type.

Owns the default session (used by run_cmd/send/expect) and all named sessions (created via open_session).

Session creation is pluggable: provide a session_factory callable to control how sessions are created (e.g. LocalSession for local hosts), or pass a ConnectionManager to use the default SSH/Telnet dispatch. Similarly, oneshot_factory controls stateless command execution.

property has_live_sessions : bool

Whether any session (default or named) is currently alive.

async run_cmd(cmd, expects=None, timeout=10.0)
Return type:

CommandStatus

async oneshot(cmd, timeout=None)
Return type:

CommandStatus

async open_session(name)

Open or reuse a named persistent shell session.

Serialized via a per-name lock with a double-checked-locking pattern: concurrent callers requesting the same name resolve to a single underlying session rather than each creating their own and clobbering the dict. Callers requesting different names take different locks and so connect concurrently.

Eagerly runs _ensure_initialized inside the lock so the stored HostSession.alive is True on return — this prevents follow-on callers from observing a just-created (un-handshaken) session as dead and recreating it.

Return type:

HostSession

async send(text)
Return type:

None

async expect(pattern, timeout=10.0)
Return type:

str

async close_all()

Close the default session and all named sessions.

Return type:

None