| 596 | 1 #!/usr/bin/env python | 
|  | 2 | 
|  | 3 """ | 
|  | 4 multiprocessing/subprocess experiments | 
|  | 5 """ | 
|  | 6 | 
|  | 7 import argparse | 
|  | 8 import os | 
| 617 | 9 import subprocess # http://bugs.python.org/issue1731717 | 
| 596 | 10 import sys | 
|  | 11 import time | 
|  | 12 import tempfile | 
|  | 13 | 
| 606 | 14 string = (str, unicode) | 
|  | 15 | 
| 601 | 16 class Process(subprocess.Popen): | 
|  | 17     """why would you name a subprocess object Popen?""" | 
|  | 18 | 
| 606 | 19     # http://docs.python.org/2/library/subprocess.html#popen-constructor | 
| 610 | 20     defaults = {'bufsize': 1, # line buffered | 
| 611 | 21                 'store_output': True, # store stdout | 
| 603 | 22                 } | 
|  | 23 | 
| 606 | 24     def __init__(self, command, **kwargs): | 
|  | 25 | 
|  | 26         # setup arguments | 
|  | 27         self.command = command | 
|  | 28         _kwargs = self.defaults.copy() | 
|  | 29         _kwargs.update(kwargs) | 
|  | 30 | 
| 607 | 31         # on unix, ``shell={True|False}`` should always come from the | 
|  | 32         # type of command (string or list) | 
| 609 | 33         if not subprocess.mswindows: | 
| 607 | 34             _kwargs['shell'] = isinstance(command, string) | 
|  | 35 | 
| 606 | 36         # output buffer | 
| 611 | 37         self.location = 0 | 
| 606 | 38         self.output_buffer = tempfile.SpooledTemporaryFile() | 
| 611 | 39         self.output = '' if _kwargs.pop('store_output') else None | 
| 606 | 40         _kwargs['stdout'] = self.output_buffer | 
|  | 41 | 
| 611 | 42         # runtime | 
|  | 43         self.start = time.time() | 
|  | 44         self.end = None | 
|  | 45 | 
| 606 | 46         # launch subprocess | 
| 607 | 47         subprocess.Popen.__init__(self, command, **_kwargs) | 
| 606 | 48 | 
| 611 | 49     def _finalize(self, process_output): | 
|  | 50         """internal function to finalize""" | 
|  | 51 | 
|  | 52         # read final output | 
|  | 53         self.read(process_output) | 
|  | 54 | 
| 614 | 55         # reset output buffer location | 
| 611 | 56         self.output_buffer.seek(0) | 
|  | 57 | 
|  | 58         # set end time | 
|  | 59         self.end = time.time() | 
|  | 60 | 
| 614 | 61     def poll(self, process_output=None): | 
|  | 62 | 
|  | 63         if process_output is not None: | 
|  | 64             self.read(process_output) # read from output buffer | 
|  | 65         poll = subprocess.Popen.poll(self) | 
|  | 66         if poll is not None: | 
|  | 67             self._finalize(process_output) | 
|  | 68         return poll | 
| 613 | 69 | 
| 611 | 70     def wait(self, maxtime=None, sleep=1., process_output=None): | 
| 606 | 71         """ | 
|  | 72         maxtime -- timeout in seconds | 
|  | 73         sleep -- number of seconds to sleep between polling | 
|  | 74         """ | 
| 614 | 75         while self.poll(process_output) is None: | 
| 606 | 76 | 
|  | 77             # check for timeout | 
|  | 78             curr_time = time.time() | 
| 613 | 79             run_time = self.runtime() | 
| 612 | 80             if maxtime is not None and run_time > maxtime: | 
| 611 | 81                 self.kill() | 
|  | 82                 self._finalize(process_output) | 
|  | 83                 return | 
| 606 | 84 | 
|  | 85             # naptime | 
|  | 86             if sleep: | 
|  | 87                 time.sleep(sleep) | 
|  | 88 | 
| 611 | 89         # finalize | 
| 613 | 90         self._finalize(process_output) | 
| 608 | 91 | 
|  | 92         return self.returncode # set by ``.poll()`` | 
| 606 | 93 | 
| 611 | 94     def read(self, process_output=None): | 
| 610 | 95         """read from the output buffer""" | 
| 611 | 96 | 
| 610 | 97         self.output_buffer.seek(self.location) | 
|  | 98         read = self.output_buffer.read() | 
| 611 | 99         if self.output is not None: | 
|  | 100             self.output += read | 
|  | 101         if process_output: | 
|  | 102             process_output(read) | 
| 610 | 103         self.location += len(read) | 
|  | 104         return read | 
|  | 105 | 
| 606 | 106     def commandline(self): | 
|  | 107         """returns string of command line""" | 
|  | 108 | 
|  | 109         if isinstance(self.command, string): | 
|  | 110             return self.command | 
|  | 111         return subprocess.list2cmdline(self.command) | 
|  | 112 | 
|  | 113     __str__ = commandline | 
|  | 114 | 
| 611 | 115     def runtime(self): | 
|  | 116         """returns time spent running or total runtime if completed""" | 
|  | 117 | 
|  | 118         if self.end is None: | 
| 613 | 119             return time.time() - self.start | 
|  | 120         return self.end - self.start | 
| 611 | 121 | 
| 596 | 122 | 
|  | 123 def main(args=sys.argv[1:]): | 
| 599 | 124     """CLI""" | 
| 596 | 125 | 
| 615 | 126     description = """demonstration of how to do things with subprocess""" | 
|  | 127 | 
| 600 | 128     # available programs | 
|  | 129     progs = {'yes': ["yes"], | 
|  | 130              'ping': ['ping', 'google.com']} | 
|  | 131 | 
| 599 | 132     # parse command line | 
| 615 | 133     parser = argparse.ArgumentParser(description=description) | 
| 596 | 134     parser.add_argument("-t", "--time", dest="time", | 
| 599 | 135                         type=float, default=4., | 
| 614 | 136                         help="seconds to run for (or <= 0 for forever)") | 
| 599 | 137     parser.add_argument("-s", "--sleep", dest="sleep", | 
| 596 | 138                         type=float, default=1., | 
| 600 | 139                         help="sleep this number of seconds between polling") | 
|  | 140     parser.add_argument("-p", "--prog", dest='program', | 
| 606 | 141                         choices=progs.keys(), default='ping', | 
| 600 | 142                         help="subprocess to run") | 
| 606 | 143     parser.add_argument("--list-programs", dest='list_programs', | 
|  | 144                         action='store_true', default=False, | 
|  | 145                         help="list available programs") | 
| 615 | 146     parser.add_argument("--wait", dest='wait', | 
|  | 147                         action='store_true', default=False, | 
|  | 148                         help="run with ``.wait()`` and a callback") | 
|  | 149     parser.add_argument("--callback", dest='callback', | 
|  | 150                         action='store_true', default=False, | 
|  | 151                         help="run with polling and a callback") | 
| 596 | 152     options = parser.parse_args(args) | 
|  | 153 | 
| 608 | 154     # list programs | 
|  | 155     if options.list_programs: | 
|  | 156         for key in sorted(progs.keys()): | 
|  | 157             print ('{}: {}'.format(key, subprocess.list2cmdline(progs[key]))) | 
| 610 | 158         sys.exit(0) | 
| 599 | 159 | 
|  | 160     # select program | 
| 600 | 161     prog = progs[options.program] | 
| 599 | 162 | 
| 611 | 163     # start process | 
| 608 | 164     proc = Process(prog) | 
| 596 | 165 | 
| 615 | 166     # demo function for processing output | 
|  | 167     def output_processor(output): | 
|  | 168         print ('[{}]:\n{}\n{}'.format(proc.runtime(), | 
|  | 169                                       output.upper(), | 
|  | 170                                       '-==-'*10)) | 
|  | 171     if options.callback: | 
|  | 172         process_output = output_processor | 
|  | 173     else: | 
|  | 174         process_output = None | 
| 613 | 175 | 
| 615 | 176     if options.wait: | 
|  | 177         # wait for being done | 
|  | 178         proc.wait(maxtime=options.time, sleep=options.sleep, process_output=output_processor) | 
|  | 179     else: | 
|  | 180         # start the main subprocess loop | 
|  | 181         while proc.poll(process_output) is None: | 
| 608 | 182 | 
| 615 | 183             if options.time > 0 and proc.runtime() > options.time: | 
|  | 184                 proc.kill() | 
| 596 | 185 | 
| 615 | 186             if options.sleep: | 
|  | 187                 time.sleep(options.sleep) | 
|  | 188 | 
| 616 | 189             if process_output is None: | 
| 615 | 190                 # process the output with ``.read()`` call | 
| 616 | 191                 read = proc.read() | 
|  | 192                 output_processor(read) | 
| 611 | 193 | 
| 614 | 194     # correctness tests | 
|  | 195     assert proc.end is not None | 
|  | 196 | 
|  | 197     # print summary | 
| 611 | 198     output = proc.output | 
|  | 199     n_lines = len(output.splitlines()) | 
| 615 | 200     print ("{}: {} lines, ran for {} seconds".format(subprocess.list2cmdline(prog), n_lines, proc.runtime())) | 
| 596 | 201 | 
|  | 202 if __name__ == '__main__': | 
|  | 203     main() |