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(command_frame=None, init_timeout=None)

Bases: ABC

Abstract base for persistent shell sessions.

A session is transport + dialect. Subclasses implement the I/O primitives (_write, _read_until_pattern, _open, close) — that is the transport (SSH, telnet, local subprocess). The dialect — how a command is wrapped in sentinels and how output/retcode are parsed back — is composed in as a CommandFrame (default BashFrame), not inherited. The base class provides the shared engine: sentinel-wrapped command execution, expect handling, and timeout recovery, all delegating the dialect to the frame.

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, on_output=None, write_progress=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, command_frame=None, init_timeout=None)

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, command_frame=None, init_timeout=None, write_chunk_size=0, write_chunk_delay=0.0)

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, log=True)

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 UnixHost.send().

Return type:

None

async expect(pattern, timeout=10.0)

Wait for a pattern in this session’s output. See UnixHost.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, command_frame=None, init_timeout=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. The shell dialect is selected by command_frame (default bash; an embedded host passes a ZephyrFrame) — it is handed to every session this manager builds, independent of the transport. A slow target’s readiness ceiling is raised via init_timeout.

property has_live_sessions : bool

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

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

CommandStatus

async oneshot(cmd, timeout=None, log=True)
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