Modern process interaction library — expect-style automation with PTY support
Project description
ptylink
Modern process interaction library for Python — expect-style automation with PTY support.
Drop-in replacement for pexpect with full type annotations, native async/await, and zero dependencies.
Why ptylink?
| pexpect | ptylink | |
|---|---|---|
| Type annotations | No | Full (mypy --strict + pyright strict) |
| Async support | Deprecated @asyncio.coroutine |
Native async/await via AsyncSpawn |
| Python 3.12+ fork warning | Yes (#817) | Fixed |
before leaks sendline echo |
Yes (#821) | Fixed |
| Dependencies | ptyprocess |
Zero |
| Performance | Baseline | 3.6–95× faster |
Installation
pip install ptylink
Requires Python 3.10+.
Quick Start
import ptylink
with ptylink.spawn("python3") as child:
child.expect(">>> ")
child.sendline("print(42)")
child.expect("42")
print(child.before) # text before the match
Context Manager
from ptylink import Spawn
with Spawn("ssh user@host") as child:
child.expect("password:")
child.sendline("secret")
child.expect(r"\$ ")
child.sendline("ls")
child.expect(r"\$ ")
print(child.before)
Async Usage
import asyncio
from ptylink import AsyncSpawn
async def main():
async with AsyncSpawn("python3") as child:
await child.expect(">>> ")
await child.sendline("1 + 1")
await child.expect("2")
asyncio.run(main())
Pipe-Based (No PTY)
For environments without PTY support (e.g. Windows):
from ptylink import PopenSpawn
with PopenSpawn("echo hello") as child:
child.expect("hello")
High-Level run()
from ptylink import run
# Simple command
output = run("ls -la")
# With exit status
output, status = run("make test", withexitstatus=True)
# Interactive with events
output = run(
"sudo apt install foo",
events={"password:": "secret\n"},
)
SSH Sessions
from ptylink import SSHSession
with SSHSession("server.example.com", username="admin") as ssh:
ssh.login(password="secret")
output = ssh.run("uname -a")
print(output)
Pattern Matching
import re
from ptylink import Spawn, EOF_TYPE, TIMEOUT_TYPE
with Spawn("some_program") as child:
# String patterns (auto-escaped)
child.expect("login:")
# Regex patterns
child.expect(re.compile(r"[\$#] "))
# Multiple patterns — returns index of match
idx = child.expect_list(["error", "success", EOF_TYPE])
if idx == 0:
print("Error:", child.after)
elif idx == 1:
print("Success!")
elif idx == 2:
print("Process ended")
API Reference
Classes and Functions
Spawn(command, *, timeout=30, encoding='utf-8', env=None, cwd=None)— PTY-based process interactionspawn(command, *, timeout=30, encoding='utf-8')— Factory function; returns aSpawninstanceAsyncSpawn(command, ...)— Async version of SpawnPopenSpawn(command, ...)— Pipe-based (no PTY) process interactionSSHSession(server, *, username=None, port=22, password=None)— SSH session helper
Spawn Methods
| Method | Description |
|---|---|
expect(pattern, *, timeout=-1) |
Wait for pattern in output |
expect_exact(pattern, *, timeout=-1) |
Wait for exact string |
expect_list(patterns, *, timeout=-1) |
Wait for any pattern, return index |
send(s) |
Send string to process |
sendline(s='') |
Send string + newline |
sendcontrol(char) |
Send control character (e.g. 'c' for Ctrl-C) |
sendeof() |
Send EOF (Ctrl-D) |
read(size=-1) |
Read from process output |
readline() |
Read a single line |
isalive() |
Check if process is running |
wait() |
Wait for exit, return exit code |
close(force=True) |
Close process and PTY |
setwinsize(rows, cols) |
Set terminal dimensions |
interact() |
Interactive passthrough mode |
Attributes
before— Text before the last matchafter— Text of the last matchmatch— Match object or string from last expect
Exceptions
PtylinkError— Base exceptionTimeout— Expect timed outEOF— Process closed outputExitStatus— Process exited with non-zero status
Sentinels
EOF_TYPE— Use in pattern lists to match EOF without raisingTIMEOUT_TYPE— Use in pattern lists to match timeout without raising
ANSI Utilities
from ptylink import strip_ansi, has_ansi
clean = strip_ansi("\x1b[31mred text\x1b[0m") # "red text"
has_ansi("\x1b[1mbold\x1b[0m") # True
Migrating from tether
This package was previously named tether. To upgrade:
pip uninstall tether
pip install ptylink
Then update your imports and any exception references:
# Before
import tether
from tether import TetherError
# After
import ptylink
from ptylink import PtylinkError
All other APIs are identical.
pexpect Migration Guide
Zero-Change Migration
# Before
import pexpect
# After — just change the import
import ptylink.compat as pexpect
All pexpect names are available: spawn, run, EOF, TIMEOUT, pxssh.
Manual Migration
| pexpect | ptylink |
|---|---|
pexpect.spawn(cmd) |
ptylink.Spawn(cmd) |
pexpect.run(cmd) |
ptylink.run(cmd) |
pexpect.EOF |
ptylink.EOF_TYPE |
pexpect.TIMEOUT |
ptylink.TIMEOUT_TYPE |
pexpect.pxssh.pxssh() |
ptylink.SSHSession() |
pexpect.spawn(cmd, async_=True) |
ptylink.AsyncSpawn(cmd) |
Breaking Changes
EOFandTIMEOUTare sentinel types, not exception classes. UseEOF_TYPEandTIMEOUT_TYPEin pattern lists.async_=Trueparameter is removed. UseAsyncSpawninstead.beforeattribute no longer contains leakedsendline()echo text.
Fixes from pexpect
- #821 —
beforeattribute no longer contains echoedsendline()input - #817 — No
ResourceWarningfromos.fork()on Python 3.12+ - #677 — No deprecated
@asyncio.coroutineusage; nativeasync defthroughout
Benchmarks
| Operation | ptylink (us/op) | pexpect (us/op) | Speedup |
|---|---|---|---|
| spawn+expect (echo) | 3,002 | 285,821 | 95x |
| spawn+expect (python) | 84,312 | 307,303 | 3.6x |
| run (echo) | 3,352 | 163,946 | 49x |
License
ISC
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ptylink-0.1.0.tar.gz.
File metadata
- Download URL: ptylink-0.1.0.tar.gz
- Upload date:
- Size: 54.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b09b80998b8b6ddd2423f4dc8bdb2b377deb69f2959e8976b2332ef0ee52d48
|
|
| MD5 |
2a7cdb3825f9c7fff4c6154188fd7dd1
|
|
| BLAKE2b-256 |
5dd6230522a72e700a1f18f1c4812cc0ca39eb74317c7f00aa17670f8989aacf
|
Provenance
The following attestation bundles were made for ptylink-0.1.0.tar.gz:
Publisher:
publish.yml on agentine/ptylink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ptylink-0.1.0.tar.gz -
Subject digest:
7b09b80998b8b6ddd2423f4dc8bdb2b377deb69f2959e8976b2332ef0ee52d48 - Sigstore transparency entry: 1102212327
- Sigstore integration time:
-
Permalink:
agentine/ptylink@c60da7c9ea9234166758ad8e286437eee61b2149 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/agentine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c60da7c9ea9234166758ad8e286437eee61b2149 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ptylink-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ptylink-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
deba02e7bd9619b73255bd1c2beb3c7e2c8830cede117aa68292de82a893f716
|
|
| MD5 |
265cc43b2cd57873c2203d4f98013c03
|
|
| BLAKE2b-256 |
1462cf5fb819a8f766cc3b593c0e5492ef67fb1248df21ece22fea15148df580
|
Provenance
The following attestation bundles were made for ptylink-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on agentine/ptylink
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ptylink-0.1.0-py3-none-any.whl -
Subject digest:
deba02e7bd9619b73255bd1c2beb3c7e2c8830cede117aa68292de82a893f716 - Sigstore transparency entry: 1102212329
- Sigstore integration time:
-
Permalink:
agentine/ptylink@c60da7c9ea9234166758ad8e286437eee61b2149 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/agentine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c60da7c9ea9234166758ad8e286437eee61b2149 -
Trigger Event:
release
-
Statement type: