iripau 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iripau/__init__.py +0 -0
- iripau/executable.py +108 -0
- iripau/functools.py +89 -0
- iripau/logging.py +86 -0
- iripau/random.py +38 -0
- iripau/shutil.py +111 -0
- iripau/subprocess.py +526 -0
- iripau/threading.py +273 -0
- iripau-0.1.0.dist-info/METADATA +21 -0
- iripau-0.1.0.dist-info/RECORD +13 -0
- iripau-0.1.0.dist-info/WHEEL +5 -0
- iripau-0.1.0.dist-info/licenses/LICENSE +373 -0
- iripau-0.1.0.dist-info/top_level.txt +1 -0
iripau/subprocess.py
ADDED
@@ -0,0 +1,526 @@
|
|
1
|
+
"""
|
2
|
+
A wrapper of the subprocess module
|
3
|
+
|
4
|
+
This module relies on the following system utilities being installed:
|
5
|
+
* bash
|
6
|
+
* kill
|
7
|
+
* pstree
|
8
|
+
* sudo
|
9
|
+
* tee
|
10
|
+
"""
|
11
|
+
|
12
|
+
import io
|
13
|
+
import os
|
14
|
+
import re
|
15
|
+
import sys
|
16
|
+
import shlex
|
17
|
+
import psutil
|
18
|
+
import subprocess
|
19
|
+
|
20
|
+
from subprocess import DEVNULL
|
21
|
+
from subprocess import PIPE
|
22
|
+
from subprocess import STDOUT
|
23
|
+
from subprocess import CompletedProcess
|
24
|
+
from subprocess import TimeoutExpired
|
25
|
+
from subprocess import SubprocessError # noqa: F401
|
26
|
+
from subprocess import CalledProcessError
|
27
|
+
|
28
|
+
from time import time
|
29
|
+
from typing import Union, Iterable, Callable
|
30
|
+
from tempfile import SpooledTemporaryFile
|
31
|
+
from contextlib import contextmanager, nullcontext
|
32
|
+
|
33
|
+
FILE = -4
|
34
|
+
GLOBAL_ECHO = False
|
35
|
+
GLOBAL_STDOUTS = set()
|
36
|
+
GLOBAL_STDERRS = set()
|
37
|
+
GLOBAL_PROMPTS = set()
|
38
|
+
|
39
|
+
|
40
|
+
TeeStream = Union[io.IOBase, Callable[[], io.IOBase]]
|
41
|
+
|
42
|
+
|
43
|
+
class PipeFile(SpooledTemporaryFile):
|
44
|
+
""" A file to be used as stdin, stdout and stderr in Popen to avoid dead lock
|
45
|
+
when the process output is too long using PIPE.
|
46
|
+
|
47
|
+
If used as stdin, the content should be written before spawning the process.
|
48
|
+
"""
|
49
|
+
|
50
|
+
def __init__(self, content=None, encoding=None, errors=None, text=None):
|
51
|
+
super().__init__(
|
52
|
+
mode="w+t" if text else "w+b",
|
53
|
+
encoding=encoding,
|
54
|
+
errors=errors
|
55
|
+
)
|
56
|
+
|
57
|
+
if content:
|
58
|
+
self.write(content)
|
59
|
+
self.seek(0)
|
60
|
+
|
61
|
+
def read_all(self):
|
62
|
+
self.seek(0)
|
63
|
+
return self.read()
|
64
|
+
|
65
|
+
|
66
|
+
class Tee(subprocess.Popen):
|
67
|
+
""" A subprocess to send real-time input to several file descriptors """
|
68
|
+
|
69
|
+
def __init__(self, input, fds, output=None, encoding=None, errors=None, text=None):
|
70
|
+
if output is STDOUT:
|
71
|
+
raise ValueError("output cannot be STDOUT")
|
72
|
+
|
73
|
+
if output is None:
|
74
|
+
output = DEVNULL
|
75
|
+
|
76
|
+
fds = normalize_outerr_fds(fds)
|
77
|
+
if 1 in fds:
|
78
|
+
if output != DEVNULL:
|
79
|
+
fds.add(2)
|
80
|
+
stdout = None
|
81
|
+
stderr = output
|
82
|
+
elif 2 in fds:
|
83
|
+
stdout = output
|
84
|
+
stderr = None
|
85
|
+
else:
|
86
|
+
stdout = output
|
87
|
+
stderr = DEVNULL
|
88
|
+
|
89
|
+
fds.discard(1)
|
90
|
+
super().__init__(
|
91
|
+
stdin=input, stdout=stdout, stderr=stderr, pass_fds=fds - {2},
|
92
|
+
encoding=encoding, errors=errors, text=text, **self.get_kwargs(fds)
|
93
|
+
)
|
94
|
+
|
95
|
+
self.output = self.stdout if self.stderr is None else self.stderr
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def get_cmd(fds):
|
99
|
+
return ["tee", "-a"] + [f"/dev/fd/{fd}" for fd in fds]
|
100
|
+
|
101
|
+
if os.access("/dev/fd/2", os.W_OK):
|
102
|
+
@classmethod
|
103
|
+
def get_kwargs(cls, fds):
|
104
|
+
return {"args": cls.get_cmd(fds)}
|
105
|
+
else:
|
106
|
+
@classmethod
|
107
|
+
def get_kwargs(cls, fds):
|
108
|
+
if 2 in fds:
|
109
|
+
return {
|
110
|
+
"args": " ".join(cls.get_cmd(fds - {2})) + " >(cat >&2)",
|
111
|
+
"shell": True
|
112
|
+
}
|
113
|
+
return {"args": cls.get_cmd(fds)}
|
114
|
+
|
115
|
+
def communicate(self, *args, **kwargs):
|
116
|
+
stdout, stderr = super().communicate(*args, **kwargs)
|
117
|
+
return stdout if stderr is None else stderr
|
118
|
+
|
119
|
+
|
120
|
+
class Popen(subprocess.Popen):
|
121
|
+
""" A subprocess.Popen that can send its stdout and stderr to several files
|
122
|
+
in real-time keeping the ability of capturing its output.
|
123
|
+
"""
|
124
|
+
|
125
|
+
def __init__(
|
126
|
+
self, args, *, cwd=None, env=None, encoding=None, errors=None, text=None,
|
127
|
+
stdout_tees: Iterable[TeeStream] = [], add_global_stdout_tees=True,
|
128
|
+
stderr_tees: Iterable[TeeStream] = [], add_global_stderr_tees=True,
|
129
|
+
prompt_tees: Iterable[TeeStream] = [], add_global_prompt_tees=True,
|
130
|
+
echo=None, alias=None, comment=None, **kwargs
|
131
|
+
):
|
132
|
+
stdout = kwargs.get("stdout")
|
133
|
+
stderr = kwargs.get("stderr")
|
134
|
+
|
135
|
+
stdout_tees, stderr_tees, prompt_tees = self._get_tee_sets(
|
136
|
+
stdout_tees, add_global_stdout_tees,
|
137
|
+
stderr_tees, add_global_stderr_tees,
|
138
|
+
prompt_tees, add_global_prompt_tees,
|
139
|
+
echo, stdout, stderr
|
140
|
+
)
|
141
|
+
|
142
|
+
if stderr is STDOUT:
|
143
|
+
err2out = True
|
144
|
+
stderr_tees = set()
|
145
|
+
else:
|
146
|
+
err2out = False
|
147
|
+
|
148
|
+
stdout_fds = {tee.fileno() for tee in stdout_tees}
|
149
|
+
stderr_fds = {tee.fileno() for tee in stderr_tees}
|
150
|
+
prompt_fds = {tee.fileno() for tee in prompt_tees}
|
151
|
+
|
152
|
+
if stdout_fds:
|
153
|
+
kwargs["stdout"] = PIPE
|
154
|
+
|
155
|
+
if stderr_fds:
|
156
|
+
kwargs["stderr"] = PIPE
|
157
|
+
|
158
|
+
if prompt_fds:
|
159
|
+
stream_prompts(prompt_fds, alias or args, cwd, env, err2out, comment)
|
160
|
+
|
161
|
+
super().__init__(args, cwd=cwd, env=env,
|
162
|
+
encoding=encoding, errors=errors, text=text, **kwargs)
|
163
|
+
|
164
|
+
self.original_stdout = self.stdout
|
165
|
+
self.original_stderr = self.stderr
|
166
|
+
|
167
|
+
stdout_process = stderr_process = self
|
168
|
+
if stdout_fds:
|
169
|
+
stdout_process = Tee(self.stdout, stdout_fds, stdout, encoding, errors, text)
|
170
|
+
self.stdout = stdout_process.output
|
171
|
+
if stderr_fds:
|
172
|
+
stderr_process = Tee(self.stderr, stderr_fds, stderr, encoding, errors, text)
|
173
|
+
self.stderr = stderr_process.output
|
174
|
+
|
175
|
+
self.stdout_process = stdout_process
|
176
|
+
self.stderr_process = stderr_process
|
177
|
+
|
178
|
+
@staticmethod
|
179
|
+
def _get_tee_files(tees: Iterable[TeeStream]):
|
180
|
+
return set(callable(tee) and tee() or tee for tee in tees)
|
181
|
+
|
182
|
+
@classmethod
|
183
|
+
def _get_tee_sets(
|
184
|
+
cls,
|
185
|
+
stdout_tees, add_global_stdout_tees,
|
186
|
+
stderr_tees, add_global_stderr_tees,
|
187
|
+
prompt_tees, add_global_prompt_tees,
|
188
|
+
echo, stdout, stderr
|
189
|
+
):
|
190
|
+
stdout_tees = set(stdout_tees)
|
191
|
+
stderr_tees = set(stderr_tees)
|
192
|
+
prompt_tees = set(prompt_tees)
|
193
|
+
|
194
|
+
if add_global_prompt_tees:
|
195
|
+
prompt_tees.update(GLOBAL_PROMPTS)
|
196
|
+
if add_global_stdout_tees:
|
197
|
+
stdout_tees.update(GLOBAL_STDOUTS)
|
198
|
+
if add_global_stderr_tees:
|
199
|
+
stderr_tees.update(GLOBAL_STDERRS)
|
200
|
+
|
201
|
+
if echo is None:
|
202
|
+
echo = GLOBAL_ECHO
|
203
|
+
|
204
|
+
if echo:
|
205
|
+
prompt_tees.add(sys.stdout)
|
206
|
+
stdout_tees.add(sys.stdout)
|
207
|
+
stderr_tees.add(sys.stderr)
|
208
|
+
|
209
|
+
if stdout is None:
|
210
|
+
if stdout_tees == {sys.stdout}:
|
211
|
+
stdout_tees.clear() # tee process not needed for stdout
|
212
|
+
if stdout_tees:
|
213
|
+
stdout_tees.add(sys.stdout)
|
214
|
+
|
215
|
+
if stderr is None:
|
216
|
+
if stderr_tees == {sys.stderr}:
|
217
|
+
stderr_tees.clear() # tee process not needed for stderr
|
218
|
+
if stderr_tees:
|
219
|
+
stderr_tees.add(sys.stderr)
|
220
|
+
|
221
|
+
return (
|
222
|
+
cls._get_tee_files(stdout_tees),
|
223
|
+
cls._get_tee_files(stderr_tees),
|
224
|
+
cls._get_tee_files(prompt_tees)
|
225
|
+
)
|
226
|
+
|
227
|
+
def get_pids(self):
|
228
|
+
""" Return the pid for all of the processes in the tree """
|
229
|
+
output = run(
|
230
|
+
["pstree", "-p", str(self.pid)],
|
231
|
+
stdin=DEVNULL,
|
232
|
+
stdout=PIPE,
|
233
|
+
stderr=DEVNULL,
|
234
|
+
text=True,
|
235
|
+
check=True
|
236
|
+
)
|
237
|
+
|
238
|
+
return re.findall("\\((\\d+)\\)", output.stdout)[::-1]
|
239
|
+
|
240
|
+
def terminate_tree(self):
|
241
|
+
run(
|
242
|
+
["sudo", "kill"] + self.get_pids(),
|
243
|
+
stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL,
|
244
|
+
)
|
245
|
+
|
246
|
+
def kill_tree(self):
|
247
|
+
run(
|
248
|
+
["sudo", "kill", "-9"] + self.get_pids(),
|
249
|
+
stdin=DEVNULL, stdout=DEVNULL, stderr=DEVNULL,
|
250
|
+
)
|
251
|
+
|
252
|
+
def end_tree(self, sigterm_timeout):
|
253
|
+
""" Try to gracefully terminate the process tree,
|
254
|
+
kill it after 'sigterm_timeout' seconds
|
255
|
+
"""
|
256
|
+
if sigterm_timeout:
|
257
|
+
self.terminate_tree()
|
258
|
+
try:
|
259
|
+
self.communicate(timeout=sigterm_timeout)
|
260
|
+
except: # noqa: E722
|
261
|
+
self.kill_tree()
|
262
|
+
else:
|
263
|
+
self.kill_tree()
|
264
|
+
|
265
|
+
def poll(self):
|
266
|
+
processes = {self.stderr_process, self.stdout_process} - {self}
|
267
|
+
if all(process.poll() is not None for process in processes):
|
268
|
+
return super().poll()
|
269
|
+
|
270
|
+
def wait(self, timeout=None):
|
271
|
+
processes = {self.stderr_process, self.stdout_process} - {self}
|
272
|
+
for process in processes:
|
273
|
+
process.wait(timeout)
|
274
|
+
timeout = None
|
275
|
+
return super().wait(timeout)
|
276
|
+
|
277
|
+
@contextmanager
|
278
|
+
def _streams_restored(self):
|
279
|
+
self.stdout = self.original_stdout
|
280
|
+
self.stderr = self.original_stderr
|
281
|
+
try:
|
282
|
+
yield
|
283
|
+
finally:
|
284
|
+
self.stdout = self.stdout_process.stdout
|
285
|
+
self.stderr = self.stderr_process.stderr
|
286
|
+
|
287
|
+
@contextmanager
|
288
|
+
def _stdin_none(self):
|
289
|
+
original_stdin = self.stdin
|
290
|
+
self.stdin = None
|
291
|
+
try:
|
292
|
+
yield
|
293
|
+
finally:
|
294
|
+
self.stdin = original_stdin
|
295
|
+
|
296
|
+
def _communicate_tees(self, timeout):
|
297
|
+
stdout = stderr = None
|
298
|
+
if self.stdout_process is not self:
|
299
|
+
stdout = self.stdout_process.communicate(timeout=timeout)
|
300
|
+
timeout = None
|
301
|
+
if self.stderr_process is not self:
|
302
|
+
stderr = self.stderr_process.communicate(timeout=timeout)
|
303
|
+
timeout = None
|
304
|
+
return stdout, stderr, timeout
|
305
|
+
|
306
|
+
def _communicate_all(self, timeout):
|
307
|
+
stdout, stderr, timeout = self._communicate_tees(timeout)
|
308
|
+
main_stdout, main_stderr = super().communicate(timeout=timeout)
|
309
|
+
return (
|
310
|
+
main_stdout if self.stdout_process is self else stdout,
|
311
|
+
main_stderr if self.stderr_process is self else stderr
|
312
|
+
)
|
313
|
+
|
314
|
+
@property
|
315
|
+
def _any_communication_started(self):
|
316
|
+
return (
|
317
|
+
self.stdout_process._communication_started or
|
318
|
+
self.stderr_process._communication_started or
|
319
|
+
self._communication_started
|
320
|
+
)
|
321
|
+
|
322
|
+
def communicate(self, input=None, timeout=None):
|
323
|
+
if self._any_communication_started and input:
|
324
|
+
raise ValueError("Cannot send input after starting communication")
|
325
|
+
|
326
|
+
with self._streams_restored():
|
327
|
+
if self.stdin and input:
|
328
|
+
self._stdin_write(input)
|
329
|
+
with self._stdin_none():
|
330
|
+
return self._communicate_all(timeout)
|
331
|
+
return self._communicate_all(timeout)
|
332
|
+
|
333
|
+
|
334
|
+
def normalize_outerr_fds(fds: Iterable[int]):
|
335
|
+
""" Return fds as a set but using 1 and 2 for stdout and stderr file
|
336
|
+
descriptors in case we are being redirected
|
337
|
+
"""
|
338
|
+
out_fd = sys.stdout.fileno() # This might not always be 1
|
339
|
+
err_fd = sys.stderr.fileno() # This might not always be 2
|
340
|
+
fds = set(fds)
|
341
|
+
if out_fd in fds:
|
342
|
+
fds.remove(out_fd)
|
343
|
+
fds.add(1)
|
344
|
+
if err_fd in fds:
|
345
|
+
fds.remove(err_fd)
|
346
|
+
fds.add(2)
|
347
|
+
return fds
|
348
|
+
|
349
|
+
|
350
|
+
def quote(cmd: Iterable[str]):
|
351
|
+
""" Convert the command tokens into a single string that could be pasted into
|
352
|
+
the shell to execute the original command
|
353
|
+
"""
|
354
|
+
return " ".join(map(shlex.quote, cmd))
|
355
|
+
|
356
|
+
|
357
|
+
def shellify(cmd: Union[str, Iterable[str]], err2out=False, comment=None):
|
358
|
+
""" Quote command if needed and optionally add extra strings to express
|
359
|
+
stderr being redirected to stdout and a comment
|
360
|
+
"""
|
361
|
+
if not isinstance(cmd, str):
|
362
|
+
cmd = quote(cmd)
|
363
|
+
if err2out:
|
364
|
+
cmd += " 2>&1"
|
365
|
+
if comment:
|
366
|
+
cmd += f" # {comment}"
|
367
|
+
return cmd
|
368
|
+
|
369
|
+
|
370
|
+
# If bash is installed and supports prompt expansion
|
371
|
+
if subprocess.run(
|
372
|
+
["bash", "-c", "echo ${0@P}"],
|
373
|
+
stdin=DEVNULL,
|
374
|
+
stdout=DEVNULL,
|
375
|
+
stderr=DEVNULL
|
376
|
+
).returncode == 0:
|
377
|
+
HOME = os.path.expanduser("~")
|
378
|
+
PS1, PS2 = subprocess.run(
|
379
|
+
["bash", "-ic", "echo \"$PS1\"; echo \"$PS2\""],
|
380
|
+
text=True,
|
381
|
+
stdin=DEVNULL,
|
382
|
+
stdout=PIPE,
|
383
|
+
stderr=DEVNULL
|
384
|
+
).stdout.splitlines()[-2:]
|
385
|
+
|
386
|
+
def stream_prompts(fds: Iterable[str], cmd, cwd=None, env=None, err2out=False, comment=None):
|
387
|
+
""" Write shell prompt and command into file descriptors fds """
|
388
|
+
fds = normalize_outerr_fds(fds)
|
389
|
+
custom_env = {"CPS1": PS1, "CPS2": PS2}
|
390
|
+
custom_env.update(env or {})
|
391
|
+
custom_env.setdefault("HOME", HOME)
|
392
|
+
script = (
|
393
|
+
"(\n"
|
394
|
+
" IFS= read -r \"line\"\n"
|
395
|
+
" echo \"${CPS1@P}${line}\"\n"
|
396
|
+
" while IFS= read line; do\n"
|
397
|
+
" echo \"${CPS2@P}${line}\"\n"
|
398
|
+
" done\n"
|
399
|
+
") | " + quote(Tee.get_cmd(fds - {1}))
|
400
|
+
)
|
401
|
+
subprocess.run(
|
402
|
+
["bash", "-c", script],
|
403
|
+
text=True,
|
404
|
+
input=shellify(cmd, err2out, comment),
|
405
|
+
stdout=None if 1 in fds else DEVNULL,
|
406
|
+
stderr=None if 2 in fds else DEVNULL,
|
407
|
+
pass_fds=fds - {1, 2},
|
408
|
+
cwd=cwd,
|
409
|
+
env=custom_env,
|
410
|
+
check=True
|
411
|
+
)
|
412
|
+
else: # Use hard-coded PS1 and PS2 strings
|
413
|
+
def stream_prompts(fds: Iterable[str], cmd, cwd=None, env=None, err2out=False, comment=None):
|
414
|
+
""" Write shell prompt and command into file descriptors fds """
|
415
|
+
cmd = shellify(cmd, err2out, comment) + "\n"
|
416
|
+
input = "$ " + "> ".join(cmd.splitlines(keepends=True))
|
417
|
+
with Tee(PIPE, fds, DEVNULL, text=True) as tee:
|
418
|
+
tee.communicate(input=input)
|
419
|
+
|
420
|
+
|
421
|
+
def set_global_echo(value):
|
422
|
+
global GLOBAL_ECHO
|
423
|
+
GLOBAL_ECHO = bool(value)
|
424
|
+
|
425
|
+
|
426
|
+
def set_global_stdout_files(*files: TeeStream):
|
427
|
+
global GLOBAL_STDOUTS
|
428
|
+
GLOBAL_STDOUTS = set(*files)
|
429
|
+
|
430
|
+
|
431
|
+
def set_global_stderr_files(*files: TeeStream):
|
432
|
+
global GLOBAL_STDERRS
|
433
|
+
GLOBAL_STDERRS = set(*files)
|
434
|
+
|
435
|
+
|
436
|
+
def set_global_prompt_files(*files: TeeStream):
|
437
|
+
global GLOBAL_PROMPTS
|
438
|
+
GLOBAL_PROMPTS = set(*files)
|
439
|
+
|
440
|
+
|
441
|
+
def _output_context(kwargs, key, encoding, errors, text):
|
442
|
+
""" Create a PipeFile, store it in kwargs[key] and return it if it is FILE.
|
443
|
+
Just return a nullcontext otherwise.
|
444
|
+
"""
|
445
|
+
if kwargs.get(key) is FILE:
|
446
|
+
kwargs[key] = PipeFile(encoding=encoding, errors=errors, text=text)
|
447
|
+
return kwargs[key]
|
448
|
+
return nullcontext()
|
449
|
+
|
450
|
+
|
451
|
+
def run(
|
452
|
+
args, *, input=None, capture_output=False, timeout=None, check=False,
|
453
|
+
encoding=None, errors=None, text=None, sigterm_timeout=10, **kwargs
|
454
|
+
):
|
455
|
+
""" A subprocess.run that instantiates this module's Popen """
|
456
|
+
if input is not None:
|
457
|
+
if kwargs.get("stdin") is not None:
|
458
|
+
raise ValueError("stdin and input arguments may not both be used.")
|
459
|
+
kwargs["stdin"] = PIPE
|
460
|
+
|
461
|
+
if capture_output:
|
462
|
+
if kwargs.get("stdout") is not None or kwargs.get("stderr") is not None:
|
463
|
+
raise ValueError("stdout and stderr arguments may not be used with capture_output.")
|
464
|
+
kwargs["stdout"] = FILE
|
465
|
+
kwargs["stderr"] = FILE
|
466
|
+
|
467
|
+
comment = f"timeout={timeout}" if timeout else None
|
468
|
+
with (
|
469
|
+
_output_context(kwargs, "stdout", encoding, errors, text) as stdout_file,
|
470
|
+
_output_context(kwargs, "stderr", encoding, errors, text) as stderr_file,
|
471
|
+
Popen(args, encoding=encoding, errors=errors, text=text,
|
472
|
+
comment=comment, **kwargs) as process
|
473
|
+
):
|
474
|
+
start = psutil.Process(process.pid).create_time()
|
475
|
+
try:
|
476
|
+
stdout, stderr = process.communicate(input, timeout=timeout)
|
477
|
+
except TimeoutExpired:
|
478
|
+
process.end_tree(sigterm_timeout)
|
479
|
+
process.wait()
|
480
|
+
raise
|
481
|
+
except: # noqa: E722
|
482
|
+
process.kill_tree()
|
483
|
+
raise
|
484
|
+
finally:
|
485
|
+
end = time()
|
486
|
+
returncode = process.poll()
|
487
|
+
|
488
|
+
if stdout_file:
|
489
|
+
stdout = stdout_file.read_all()
|
490
|
+
if stderr_file:
|
491
|
+
stderr = stderr_file.read_all()
|
492
|
+
|
493
|
+
if check and returncode:
|
494
|
+
raise CalledProcessError(returncode, process.args, output=stdout, stderr=stderr)
|
495
|
+
|
496
|
+
output = CompletedProcess(process.args, returncode, stdout, stderr)
|
497
|
+
output.time = end - start
|
498
|
+
return output
|
499
|
+
|
500
|
+
|
501
|
+
def call(*args, **kwargs):
|
502
|
+
return run(*args, **kwargs).returncode
|
503
|
+
|
504
|
+
|
505
|
+
def check_call(*args, **kwargs):
|
506
|
+
kwargs["check"] = True
|
507
|
+
return call(*args, **kwargs)
|
508
|
+
|
509
|
+
|
510
|
+
def check_output(*args, **kwargs):
|
511
|
+
kwargs["check"] = True
|
512
|
+
kwargs.setdefault("stdout", PIPE)
|
513
|
+
return run(*args, **kwargs).stdout
|
514
|
+
|
515
|
+
|
516
|
+
def getoutput(*args, **kwargs):
|
517
|
+
return getstatusoutput(*args, **kwargs)[1]
|
518
|
+
|
519
|
+
|
520
|
+
def getstatusoutput(*args, **kwargs):
|
521
|
+
kwargs["stdout"] = PIPE
|
522
|
+
kwargs["stderr"] = STDOUT
|
523
|
+
kwargs["shell"] = True
|
524
|
+
kwargs["text"] = True
|
525
|
+
output = run(*args, **kwargs)
|
526
|
+
return (output.returncode, output.stdout)
|