Skip to main content

Sioba Subprocess IO Interface layer

Project description

sioba_subprocess — subprocess/PTY interface for sioba

sioba_subprocess adds an exec:// interface to sioba so you can drive a local shell or program as an interactive terminal session behind the same async Interface API (screen buffer, cursor, callbacks, etc.).

  • POSIX: forks a child attached to a PTY (via pty, subprocess, termios) and mirrors stdout/stderr to the sioba buffer; window size changes are propagated with ioctl(TIOCSWINSZ) + SIGWINCH.
  • Windows: uses pywinpty to spawn and attach to a console app (e.g., cmd.exe or PowerShell), forwarding I/O to sioba’s buffer.

The package is discovered by sioba via entry point:
"sioba.interface".exec = "sioba_subprocess.interface:ShellInterface"


Installation

# sioba core + this plugin
pip install sioba sioba_subprocess
# or with uv
uv pip install sioba sioba_subprocess
  • Python ≥ 3.10
  • Windows: pywinpty installs automatically via extras.

Quickstart

Start your default shell

import asyncio
from sioba import interface_from_uri

async def main():
    # POSIX default: /bin/bash; Windows default: cmd.exe
    sh = await interface_from_uri("exec://").start()

    # capture everything the program prints
    screen = []
    sh.on_send_to_frontend(lambda _i, data: screen.append(data))

    # run a command
    await sh.receive_from_frontend(b"echo hello from sioba\r\n")
    await asyncio.sleep(0.2)  # give the subprocess a moment

    print(b"".join(screen).decode("utf-8", errors="replace"))
    await sh.shutdown()

asyncio.run(main())

Launch a specific program (absolute path)

import asyncio, sys, pathlib
from sioba import interface_from_uri

async def main():
# Start an interactive Python REPL
    py = pathlib.Path(sys.executable)
    iface = await interface_from_uri(f"exec:///{py}").start()

    await iface.receive_from_frontend(b"import hashlib\n")
    await iface.receive_from_frontend(b'hashlib.md5(b\"hello world\").hexdigest()\n')
    await asyncio.sleep(0.5)

    print(iface.get_terminal_buffer().decode("utf-8", "replace"))
    await iface.receive_from_frontend(b"exit()\n")
    await asyncio.sleep(0.2)

asyncio.run(main())

Pass arguments & working directory

You can pass arguments either as kwargs or via query params:

# kwargs (recommended)
sh = await interface_from_uri(
    "exec:///bin/bash",
    invoke_args=["-lc", "echo $PWD && ls -1"],
    invoke_cwd="/tmp",
).start()

# or query params (repeatable ?arg=…; values are strings)
sh = await interface_from_uri("exec:///bin/bash?arg=-lc&arg=echo%20hello").start()

Resizing the terminal & reading the screen

# tell the interface (and the PTY/console) about UI size changes
sh.update_terminal_metadata({"rows": 40, "cols": 120})

# snapshot of what the user would see
buf = sh.get_terminal_buffer()
print(buf.decode("utf-8", "replace"))

What you get

  • Interface: ShellInterface (scheme exec://) — inherits sioba’s Interface

  • Context: ShellContext (extends InterfaceContext)

    • invoke_args: list[str] — CLI args for the child process
    • invoke_cwd: Optional[str] — working directory for the child
  • Discovery: installed as a sioba plugin via entry point; after installation:

from sioba import list_schemes
assert "exec" in list_schemes()

URI reference

exec:///<ABSOLUTE_PATH>?arg=<value>&arg=<value>&invoke_cwd=<path>
  • Path: absolute path to the program. If omitted, defaults to:

    • POSIX: /bin/bash
    • Windows: cmd.exe
  • Query params

    • arg: repeatable; each occurrence becomes the next CLI argument.
    • invoke_cwd: working directory string.
  • General sioba params (handled by InterfaceContext.from_uri):

    • rows, cols, encoding, convertEol, auto_shutdown,
    • scrollback_buffer_uri (terminal:// by default), scrollback_buffer_size, etc.

Windows path tip: prefer exec:///C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe (leading slash is stripped automatically; forward slashes are OK).


Lifecycle & behavior

  • await iface.start() spawns the child attached to a PTY/console and begins reader threads.

  • I/O

    • await iface.receive_from_frontend(b"...\n") → writes to the child stdin/console.
    • Child output → await iface.send_to_frontend(...)Buffer (ANSI aware if terminal://) → your callbacks.
    • End-of-line normalization honors convertEol (enabled by default).
  • Resize: update_terminal_metadata({"rows": R, "cols": C}) adjusts PTY/console size.

  • Shutdown

    • POSIX tries SIGTERM on the process group, then escalates to SIGKILL if needed.
    • Windows terminates via pywinpty.
    • If the child exits by itself, the interface transitions to InterfaceState.SHUTDOWN automatically and runs on_shutdown hooks.

Examples

Drop into PowerShell (Windows)

sh = await interface_from_uri(
    "exec:///C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe"
).start()

Minimal echo of everything printed

out = []
sh.on_send_to_frontend(lambda _i, data: out.append(data))

Troubleshooting

  • No output? Make sure you send a newline (\n) so the shell executes a line. On Windows, convertEol=True will handle \r\n vs \n.
  • Windows path parsing: Use exec:///C:/path/to/app.exe (triple slash + absolute path with drive letter). The implementation strips the leading / on Windows.
  • Separate stdout/stderr? Not currently split — both are merged into the PTY stream (see tests). TODO: consider optional channels for stderr.

Security

Launching exec:// runs arbitrary local programs. Do not feed untrusted URIs or user input to interface_from_uri without validation.


Developing & testing

# in this repo
uv sync
uv run pytest -q
# (or) pytest -q

The test suite demonstrates:

  • Spawning exec:///python and evaluating expressions interactively
  • Clean shutdown when the child exits or on request
  • Passing CLI args via ?arg= and handling error exit codes

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sioba_subprocess-0.4.20250925.3.tar.gz (8.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sioba_subprocess-0.4.20250925.3-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

Details for the file sioba_subprocess-0.4.20250925.3.tar.gz.

File metadata

File hashes

Hashes for sioba_subprocess-0.4.20250925.3.tar.gz
Algorithm Hash digest
SHA256 be20dcbf998853487406bc2e4601a41a4f68a0c1a13c0c09bb1e865d4c9790cd
MD5 91278f8917e64db6fad15503e4a8bc12
BLAKE2b-256 058234efe6c618a3008f78dc991f5c8e2acce40ce22e3baf8c0360c2256cb5f5

See more details on using hashes here.

File details

Details for the file sioba_subprocess-0.4.20250925.3-py3-none-any.whl.

File metadata

File hashes

Hashes for sioba_subprocess-0.4.20250925.3-py3-none-any.whl
Algorithm Hash digest
SHA256 09941cff72ce83dd7272286bd9f6ddefda95075f50602481b3b834658230c572
MD5 e667f8094f4cef86aec6fc86f3a41771
BLAKE2b-256 f38edc71505ac335e1e4f9a53cb28afa27f4ea469f9e0d81a57bdd8aebef21d2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page