ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/pyradlib/pyrad_proc.py
Revision: 1.1
Committed: Mon Mar 28 17:52:11 2016 UTC (9 years, 1 month ago) by schorsch
Content type: text/x-python
Branch: MAIN
Log Message:
Support library for test suite and other Pytho scripts

File Contents

# Content
1 # -*- coding: utf-8 -*-
2 ''' pyrad_proc.py - Process and pipeline management for Python Radiance scripts
3 2016 - Georg Mischler
4
5 Use as:
6 from pyradlib.pyrad_proc import PIPE, Error, ProcMixin
7
8 For a single-file installation, include the contents of this file
9 at the same place (minus the __future__ import below).
10 '''
11 from __future__ import division, print_function, unicode_literals
12
13 import sys
14 import subprocess
15 PIPE = subprocess.PIPE
16
17
18 class Error(Exception): pass
19
20
21 class ProcMixin():
22 '''Process and pipeline management for Python Radiance scripts
23 '''
24 def raise_on_error(self, actstr, e):
25 raise Error('Unable to %s - %s' % (actstr, str(e)))
26
27 def __configure_subprocess(self):
28 '''Prevent subprocess module failure in frozen scripts on Windows.
29 Prevent console windows from popping up when not console based.
30 Make sure we use the version specific string types.
31 '''
32 # On Windows, sys.stdxxx may not be available when:
33 # - built as *.exe with "pyinstaller --noconsole"
34 # - invoked via CreateProcess() and stream not redirected
35 try:
36 sys.__stdin__.fileno()
37 self._stdin = sys.stdin
38 except: self._stdin = PIPE
39 try:
40 sys.__stdout__.fileno()
41 self._stdout = sys.stdout
42 except: self._stdout = PIPE
43 try:
44 sys.__stderr__.fileno()
45 self._stderr = sys.stderr
46 # keep subprocesses from opening their own console.
47 except: self._stderr = PIPE
48 if hasattr(subprocess, 'STARTUPINFO'):
49 si = subprocess.STARTUPINFO()
50 si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
51 self._pipeargs = {'startupinfo':si}
52 else: self._pipeargs = {}
53 # type names vary between Py2.7 and 3.x
54 self._strtypes = (type(b''), type(u''))
55 # private attribute to indicate established configuration
56 self.__proc_mixin_setup = True
57
58 def qjoin(self, sl):
59 '''Join a list with quotes around each element containing whitespace.
60 We only use this to display command lines on sys.stderr, the actual
61 Popen() calls are made with the original list.
62 '''
63 def _q(s):
64 if ' ' in s or '\t' in s or ';' in s:
65 return "'" + s + "'"
66 return s
67 return ' '.join([_q(s) for s in sl])
68
69 def call_one(self, cmdl, actstr, _in=None, out=None,
70 universal_newlines=False):
71 '''Create a single subprocess, possibly with an incoming and outgoing
72 pipe at each end.
73 - cmdl
74 A list of strings, leading with the name of the executable (without
75 the *.exe suffix), followed by individual arguments.
76 - actstr
77 A text string of the form "do something".
78 Used in verbose mode as "### do someting\\n### [command line]"
79 Used in error messages as "Scriptname: Unable to do something".
80 - _in / out
81 What to do with the input and output pipes of the process:
82 * a filename as string
83 Open file and use for reading/writing.
84 * a file like object
85 Use for reading/writing
86 * PIPE
87 Pipe will be available in returned object for reading/writing.
88 * None (default)
89 System stdin/stdout will be used if available
90 If _in or out is a PIPE, the caller should call p.wait() on the
91 returned Popen instance after writing to and closing it.
92 '''
93 try: self.__proc_mixin_setup
94 except AttributeError: self.__configure_subprocess()
95 if _in == PIPE: stdin = _in
96 elif isinstance(_in, self._strtypes): stdin = open(_in, 'rb')
97 elif hasattr(_in, 'read'): stdin = _in
98 else: stdin = self._stdin
99 if out == PIPE: stdout = out
100 elif isinstance(out, self._strtypes): stdout = open(out, 'wb')
101 elif hasattr(out, 'write'): stdout = out
102 else: stdout = self._stdout
103 displ = cmdl[:]
104 if isinstance(_in, self._strtypes): displ[:0] = [_in, '>']
105 if isinstance(out, self._strtypes): displ.extend(['>', out])
106 if getattr(self, 'verbose', None):
107 sys.stderr.write('### %s \n' % actstr)
108 sys.stderr.write(self.qjoin(displ) + '\n')
109 if not getattr(self, 'donothing', None):
110 try: p = subprocess.Popen(cmdl, stdin=stdin, stdout=stdout,
111 stderr=self._stderr,
112 universal_newlines=universal_newlines, **self._pipeargs)
113 except Exception as e:
114 self.raise_on_error(actstr, str(e))
115 if stdin != PIPE and stdout != PIPE:
116 # caller needs to wait after reading or writing (else deadlock)
117 res = p.wait()
118 if res != 0:
119 self.raise_on_error(actstr,
120 'Nonzero exit (%d) from command [%s].'
121 % (res, self.qjoin(displ)))
122 return p
123
124 def call_two(self, cmdl_1, cmdl_2, actstr_1, actstr_2, _in=None, out=None,
125 universal_newlines=False):
126 '''Create two processes, chained via a pipe, possibly with an incoming
127 and outgoing pipe at each end.
128 Returns a tuple of two Popen instances.
129 Arguments are equivalent to call_one(), with _in and out applying
130 to the ends of the chain.
131 If _in or out is PIPE, the caller should call p.wait() on both
132 returned popen instances after writing to and closing the first on .
133 '''
134 try: self.__proc_mixin_setup
135 except AttributeError: self.__configure_subprocess()
136 if _in == PIPE: stdin = _in
137 elif isinstance(_in, self._strtypes): stdin = open(_in, 'rb')
138 elif hasattr(_in, 'read'): stdin = _in
139 else: stdin = self._stdin
140 outendstr = '\n'
141 if out == PIPE:
142 stdout = out
143 elif isinstance(out, self._strtypes):
144 stdout = open(out, 'wb')
145 outendstr = ' > "%s"\n' % out
146 elif hasattr(out, 'write'):
147 stdout = out
148 outendstr = ' > "%s"\n' % out.name
149 else: stdout = self._stdout
150 if getattr(self, 'verbose', None):
151 sys.stderr.write('### %s \n' % actstr_1)
152 sys.stderr.write('### %s \n' % actstr_2)
153 sys.stderr.write(self.qjoin(cmdl_1) + ' | ')
154 if not getattr(self, 'donothing', None):
155 try: p1 = subprocess.Popen(cmdl_1, stdin=stdin,
156 stdout=PIPE, stderr=self._stderr, **self._pipeargs)
157 except Exception as e:
158 self.raise_on_error(actstr_1, str(e))
159 if getattr(self, 'verbose', None):
160 sys.stderr.write(self.qjoin(cmdl_2) + outendstr)
161 if not getattr(self, 'donothing', None):
162 try:
163 p2 = subprocess.Popen(cmdl_2, stdin=p1.stdout, stdout=stdout,
164 stderr=self._stderr,
165 universal_newlines=universal_newlines, **self._pipeargs)
166 p1.stdout.close()
167 except Exception as e:
168 self.raise_on_error(actstr_2, str(e))
169 if stdin != PIPE and stdout != PIPE:
170 # caller needs to wait after reading or writing (else deadlock)
171 res = p1.wait()
172 if res != 0:
173 self.raise_on_error(actstr_1,
174 'Nonzero exit (%d) from command [%s].'
175 % (res, self.qjoin(cmdl_1)))
176 res = p2.wait()
177 if res != 0:
178 self.raise_on_error(actstr_2,
179 'Nonzero exit (%d) from command [%s].'
180 % (res, self.qjoin(cmdl_2)))
181 return p1, p2
182
183
184 def call_many(self, cmdlines, actstr, _in=None, out=None,
185 universal_newlines=False):
186 '''Create a series of N processes, chained via pipes, possibly with an
187 incoming and outgoing pipe at each end.
188 Returns a tuple of N subprocess.Popen instances.
189 Depending on the values of _in and out, the first and last may be
190 available to write to or read from respectively.
191 Most arguments are equivalent to call_one(), with
192 - cmdlines
193 a list of N command argument lists
194 - _in / out
195 applying to the ends of the chain.
196 If _in or out is PIPE, the caller should call p.wait() on all
197 returned Popen instances after writing to and closing the first one.
198 '''
199 if len(cmdlines) == 1:
200 # other than direct call_one(), this returns a one-item tuple!
201 return (self.call_one(cmdlines[0], actstr, _in=_in, out=out,
202 universal_newlines=universal_newlines),)
203 try: self.__proc_mixin_setup
204 except AttributeError: self.__configure_subprocess()
205 if _in == PIPE: stdin = _in
206 elif isinstance(_in, self._strtypes): stdin = open(_in, 'rb')
207 elif hasattr(_in, 'read'): stdin = _in
208 else: stdin = self._stdin
209 outendstr = '\n'
210 if out == PIPE:
211 stdout = out
212 elif isinstance(out, self._strtypes):
213 stdout = open(out, 'wb')
214 outendstr = ' > "%s"\n' % out
215 elif hasattr(out, 'write'):
216 stdout = out
217 outendstr = ' > "%s"\n' % out.name
218 else: stdout = self._stdout
219 procs = []
220 if getattr(self, 'verbose', None):
221 sys.stderr.write('### %s \n' % actstr)
222 sys.stderr.write(self.qjoin(cmdlines[0]) + ' | ')
223 if not getattr(self, 'donothing', None):
224 try:
225 prevproc = subprocess.Popen(cmdlines[0], stdin=stdin,
226 stdout=PIPE, stderr=self._stderr, **self._pipeargs)
227 procs.append(prevproc)
228 except Exception as e:
229 self.raise_on_error(actstr, str(e))
230
231 for cmdl in cmdlines[1:-1]:
232 if getattr(self, 'verbose', None):
233 sys.stderr.write(self.qjoin(cmdl) + ' | ')
234 if not getattr(self, 'donothing', None):
235 try:
236 nextproc = subprocess.Popen(cmdl, stdin=prevproc.stdout,
237 stdout=PIPE, stderr=self._stderr, **self._pipeargs)
238 procs.append(nextproc)
239 prevproc.stdout.close()
240 prevproc = nextproc
241 except Exception as e:
242 self.raise_on_error(actstr, str(e))
243
244 if getattr(self, 'verbose', None):
245 sys.stderr.write(self.qjoin(cmdlines[-1]) + outendstr)
246 if not getattr(self, 'donothing', None):
247 try:
248 lastproc = subprocess.Popen(cmdlines[-1], stdin=prevproc.stdout,
249 stdout=stdout, stderr=self._stderr,
250 universal_newlines=universal_newlines, **self._pipeargs)
251 prevproc.stdout.close()
252 procs.append(lastproc)
253 prevproc.stdout.close()
254 except Exception as e:
255 self.raise_on_error(actstr, str(e))
256
257 if stdin != PIPE and stdout!= PIPE:
258 # caller needs to wait after reading or writing (else deadlock)
259 for proc, cmdl in zip(procs, cmdlines):
260 res = proc.wait()
261 if res != 0:
262 self.raise_on_error(actstr,
263 'Nonzero exit (%d) from command [%s].'
264 % (res, self.qjoin(cmdl)))
265 return procs
266
267
268 ### end of proc_mixin.py