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.20250928.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.20250928-py3-none-any.whl (11.4 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for sioba_subprocess-0.4.20250928.tar.gz
Algorithm Hash digest
SHA256 d1a9fc52c46c2f0e254c33a9674f3d1df2620cc1f957123d217784f73a814d3f
MD5 0e6f496540c15ef00d7135a115c24997
BLAKE2b-256 28ee08cd6bb728fa24c1d909f2e036afd5156b566836cedd3d94f7560d067b99

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for sioba_subprocess-0.4.20250928-py3-none-any.whl
Algorithm Hash digest
SHA256 bf839582fa574b7d94f9e1685466251cd80f255f4e2df0d7674d41290d6473e8
MD5 1396db6e3cf1529796bfbf7453e9abc5
BLAKE2b-256 fc9980aaa43d9d37bf9245de6e2a7cf5265bea27bfb1a26ced42a155b6f58817

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