Threading
SSHScript v3.1 does not replace the process-wide threading.Thread class. Regular Python code should make Session ownership explicit. Transformed .spy files additionally provide scoped Session inheritance for recognized Thread constructors.
Regular Python: own a Session per worker
Creating and closing the Session inside each worker makes lifetime and failure boundaries clear:
from concurrent.futures import ThreadPoolExecutor
from sshscript import Session
def remote_hostname(host):
local = Session()
try:
with local.connect(host) as remote:
stdout, stderr = remote.exec_command(
"hostname",
shell=False,
)
return host, str(stdout).strip()
finally:
local.close(strict=True)
hosts = [
"ops@web-1.example.net",
"ops@web-2.example.net",
]
with ThreadPoolExecutor(max_workers=len(hosts)) as pool:
for host, hostname in pool.map(remote_hostname, hosts):
print(host, hostname)
Each worker verifies host keys and authenticates independently. Futures also return worker exceptions to the main thread when results are consumed.
When workers need a shared input value, pass it as a normal function argument. Do not rely on an implicit global Session in an ordinary .py module.
.spy files: scoped Session inheritance
The .spy source transformer recognizes threading.Thread(...) and imported Thread(...) constructors. It creates a standard Thread that snapshots the active SSHScript Session stack when the Thread is constructed, installs that stack only while the target runs, and clears it afterward.
import threading
def check_host():
$hostname
print($.stdout.strip())
with $.connect("ops@example.net"):
worker = threading.Thread(target=check_host)
worker.start()
worker.join()
The worker therefore sees the connected Session that was active at construction time. This behavior belongs to transformed .spy code only; it does not monkey-patch other libraries or ordinary Python threads.
Collect failures
An exception printed by a raw worker Thread does not automatically become an exception in the caller. Store worker results and errors, use concurrent.futures, or explicitly raise collected failures after join(). A test must not pass merely because an assertion failed in a background Thread.
The credentialed language.spy threaded mode follows this rule by collecting worker failures and raising them in the main thread.
Test without SSH credentials
The credential-free test suite verifies connected Session inheritance with in-memory SSH clients:
python3 -m unittest discover -v -s unittest -p 'test_*.py'
See Development and Testing for the complete release gate and the separate manual integration modes.
Last Updated: 2026-09-14 18:02:02