Skip to content

Commit ae932bc

Browse files
authored
Stdin write (#257)
* Added handling EAGAIN on stdin streams for run_command host output * Updated changelog * Updated docs
1 parent 5d21eee commit ae932bc

File tree

3 files changed

+43
-3
lines changed

3 files changed

+43
-3
lines changed

Changelog.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ Changes
1010
* Added interactive shell support to single and parallel clients - see `documentation <https://parallel-ssh.readthedocs.io/en/latest/advanced.html#interactive-shells>`_.
1111
* Added ``pssh.utils.enable_debug_logger`` function.
1212
* ``ParallelSSHClient`` timeout parameter is now also applied to *starting* remote commands via ``run_command``.
13-
* Assigning to ``ParallelSSHClient.hosts`` cleans up clients of hosts no longer in host list - #220
13+
* ``HostOutput.stdin`` now handles EAGAIN automatically when writing - #165.
14+
* Assigning to ``ParallelSSHClient.hosts`` cleans up clients of hosts no longer in host list - #220.
1415

1516
Fixes
1617
-----

pssh/clients/base/single.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,38 @@
4343
logger = logging.getLogger(__name__)
4444

4545

46+
class Stdin(object):
47+
"""Stdin stream for a channel.
48+
49+
Handles EAGAIN.
50+
51+
Provides ``write`` and ``flush`` only.
52+
"""
53+
__slots__ = ('_channel', '_client')
54+
55+
def __init__(self, channel, client):
56+
"""
57+
:param channel: The channel the stdin stream is from.
58+
:type channel: IO object
59+
:param client: The SSH client the channel is from.
60+
:type client: ``BaseSSHClient``
61+
"""
62+
self._channel = channel
63+
self._client = client
64+
65+
def write(self, data):
66+
"""Write to stdin.
67+
68+
:param data: Data to write.
69+
:type data: str
70+
"""
71+
return self._client._eagain(self._channel.write, data)
72+
73+
def flush(self):
74+
"""Flush pending data written to stdin."""
75+
return self._client._eagain(self._channel.flush)
76+
77+
4678
class InteractiveShell(object):
4779
"""
4880
Run commands on an interactive shell.
@@ -304,9 +336,8 @@ def _make_host_output(self, channel, encoding, read_timeout):
304336
_buffers = HostOutputBuffers(
305337
stdout=BufferData(rw_buffer=_stdout_buffer, reader=_stdout_reader),
306338
stderr=BufferData(rw_buffer=_stderr_buffer, reader=_stderr_reader))
307-
stdin = channel
308339
host_out = HostOutput(
309-
host=self.host, channel=channel, stdin=stdin,
340+
host=self.host, channel=channel, stdin=Stdin(channel, self),
310341
client=self, encoding=encoding, read_timeout=read_timeout,
311342
buffers=_buffers,
312343
)

tests/native/test_single_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ def test_stderr(self):
9494
self.assertListEqual(expected, stderr)
9595
self.assertTrue(len(output) == 0)
9696

97+
def test_stdin(self):
98+
host_out = self.client.run_command('read line; echo $line')
99+
host_out.stdin.write('a line\n')
100+
host_out.stdin.flush()
101+
self.client.wait_finished(host_out)
102+
stdout = list(host_out.stdout)
103+
self.assertListEqual(stdout, ['a line'])
104+
97105
def test_long_running_cmd(self):
98106
host_out = self.client.run_command('sleep 2; exit 2')
99107
self.assertRaises(ValueError, self.client.wait_finished, host_out.channel)

0 commit comments

Comments
 (0)