@@ -177,21 +177,11 @@ def __init__(self, hosts,
177177 >>> import paramiko
178178 >>> client_key = paramiko.RSAKey.from_private_key_file('user.key')
179179 >>> client = ParallelSSHClient(['myhost1', 'myhost2'], pkey=client_key)
180-
181- **Example with expression as host list**
182180
183- Any type of iterator may be used as host list, including generator and
184- list comprehension expressions.
181+ **Multiple commands**
185182
186- >>> hosts = ['dc1.myhost1', 'dc2.myhost2']
187- >>> client = ParallelSSHClient([h for h in hosts if h.find('dc1')])
188- >>> client.run_command(<..>)
189-
190- **Overriding host list**
191-
192- >>> client.hosts = ['otherhost']
193- >>> print client.run_command('exit 0')
194- >>> {'otherhost': {'exit_code':0}, <..>}
183+ >>> for cmd in ['uname', 'whoami']:
184+ ... client.run_command(cmd)
195185
196186 .. note ::
197187
@@ -225,7 +215,7 @@ def __init__(self, hosts,
225215 self .timeout = timeout
226216 self .proxy_host , self .proxy_port = proxy_host , proxy_port
227217 # To hold host clients
228- self .host_clients = dict (( host , None ) for host in hosts )
218+ self .host_clients = {}
229219 self .agent = agent
230220
231221 def run_command (self , * args , ** kwargs ):
@@ -259,9 +249,9 @@ def run_command(self, *args, **kwargs):
259249 :raises: :mod:`pssh.exceptions.UnknownHostException` on DNS resolution error
260250 :raises: :mod:`pssh.exceptions.ConnectionErrorException` on error connecting
261251 :raises: :mod:`pssh.exceptions.SSHException` on other undefined SSH errors
262-
252+
263253 **Example Usage**
264-
254+
265255 **Simple run command**
266256
267257 >>> output = client.run_command('ls -ltrh')
@@ -291,41 +281,79 @@ def run_command(self, *args, **kwargs):
291281
292282 Capture stdout - **WARNING** - this will store the entirety of stdout
293283 into memory and may exhaust available memory if command output is
294- large enough:
284+ large enough.
285+
286+ Iterating over stdout/stderr by definition implies blocking until
287+ command has finished. To only see output as it comes in without blocking
288+ the host logger can be enabled - see `Enabling Host Logger` above.
295289
296290 >>> for host in output:
297291 >>> stdout = list(output[host]['stdout'])
298292 >>> print "Complete stdout for host %s is %s" % (host, stdout,)
299-
293+
294+ **Expression as host list**
295+
296+ Any type of iterator may be used as host list, including generator and
297+ list comprehension expressions.
298+
299+ >>> hosts = ['dc1.myhost1', 'dc2.myhost2']
300+ # List comprehension
301+ >>> client = ParallelSSHClient([h for h in hosts if h.find('dc1')])
302+ # Generator
303+ >>> client = ParallelSSHClient((h for h in hosts if h.find('dc1')))
304+ # Filter
305+ >>> client = ParallelSSHClient(filter(lambda h: h.find('dc1'), hosts))
306+ >>> client.run_command(<..>)
307+
308+ .. note ::
309+
310+ Since iterators by design only iterate over a sequence once then stop,
311+ `client.hosts` should be re-assigned after each call to `run_command`
312+ when using iterators as target of `client.hosts`.
313+
314+ **Overriding host list**
315+
316+ Host list can be modified in place. Call to `run_command` will create
317+ new connections as necessary and output will only contain output for
318+ hosts command ran on.
319+
320+ >>> client.hosts = ['otherhost']
321+ >>> print client.run_command('exit 0')
322+ >>> {'otherhost': {'exit_code':0}, <..>}
323+
300324 **Run multiple commands in parallel**
301325
302- This short example demonstrates running long running commands in parallel
303- and how long it takes for all commands to start, blocking until they
304- complete and how long it takes for all commands to complete.
305-
306- See examples directory for complete example script. ::
326+ This short example demonstrates running long running commands in
327+ parallel, how long it takes for all commands to start, blocking until
328+ they complete and how long it takes for all commands to complete.
307329
308- output = []
330+ See examples directory for complete script. ::
309331
310- start = datetime.datetime.now()
311- cmds = ['sleep 5' for _ in xrange(10)]
312- for cmd in cmds:
313- output.append(client.run_command(cmd, stop_on_errors=False))
314- end = datetime.datetime.now()
315- print "Started %s commands in %s" % (len(cmds), end-start,)
316- start = datetime.datetime.now()
317- for _output in output:
318- for line in _output[host]['stdout']:
319- print line
320- end = datetime.datetime.now()
321- print "All commands finished in %s" % (end-start,)
332+ output = []
333+ host = 'localhost'
334+
335+ # Run 10 five second sleeps
336+ cmds = ['sleep 5' for _ in xrange(10)]
337+ start = datetime.datetime.now()
338+ for cmd in cmds:
339+ output.append(client.run_command(cmd, stop_on_errors=False))
340+ end = datetime.datetime.now()
341+ print "Started %s commands in %s" % (len(cmds), end-start,)
342+ start = datetime.datetime.now()
343+ for _output in output:
344+ for line in _output[host]['stdout']:
345+ print line
346+ end = datetime.datetime.now()
347+ print "All commands finished in %s" % (end-start,)
322348
323349 *Output*
324350
325- Started 10 commands in 0:00:00.428629
326- All commands finished in 0:00:05.014757
351+ ::
327352
328- **Example Output**
353+ Started 10 commands in 0:00:00.428629
354+ All commands finished in 0:00:05.014757
355+
356+ **Output dictionary**
329357
330358 ::
331359
@@ -575,7 +603,7 @@ def copy_file(self, local_file, remote_file, recurse=False):
575603
576604 def _copy_file (self , host , local_file , remote_file , recurse = False ):
577605 """Make sftp client, copy file"""
578- if not self .host_clients [host ]:
606+ if not host in self . host_clients or not self .host_clients [host ]:
579607 self .host_clients [host ] = SSHClient (
580608 host , user = self .user , password = self .password ,
581609 port = self .port , pkey = self .pkey ,
0 commit comments