mirror of
https://github.com/YunoHost/moulinette.git
synced 2024-09-03 20:06:31 +02:00
[enh] Add a process module to the utils
This commit is contained in:
parent
f8f8bfca79
commit
8afa2dbe81
1 changed files with 148 additions and 0 deletions
148
moulinette/utils/process.py
Normal file
148
moulinette/utils/process.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import subprocess
|
||||||
|
try:
|
||||||
|
from pipes import quote # Python2 & Python3 <= 3.2
|
||||||
|
except ImportError:
|
||||||
|
from shlex import quote # Python3 >= 3.3
|
||||||
|
|
||||||
|
from .stream import NonBlockingStreamReader
|
||||||
|
|
||||||
|
# Prevent to import subprocess only for common classes
|
||||||
|
CalledProcessError = subprocess.CalledProcessError
|
||||||
|
|
||||||
|
|
||||||
|
# Alternative subprocess methods ---------------------------------------
|
||||||
|
|
||||||
|
def check_output(args, stderr=subprocess.STDOUT, shell=True, **kwargs):
|
||||||
|
"""Run command with arguments and return its output as a byte string
|
||||||
|
|
||||||
|
Overwrite some of the arguments to capture standard error in the result
|
||||||
|
and use shell by default before calling subprocess.check_output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return subprocess.check_output(args, stderr=stderr, shell=shell, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# Call with stream access ----------------------------------------------
|
||||||
|
|
||||||
|
def call_async_output(args, callback, **kwargs):
|
||||||
|
"""Run command and provide its output asynchronously
|
||||||
|
|
||||||
|
Run command with arguments and wait for it to complete to return the
|
||||||
|
returncode attribute. The callback must take one byte string argument
|
||||||
|
and will be called each time the command produces some output.
|
||||||
|
|
||||||
|
The stdout and stderr additional arguments for the Popen constructor
|
||||||
|
are not allowed as they are used internally.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- args -- String or sequence of program arguments
|
||||||
|
- callback -- Method or object to call with output as argument
|
||||||
|
- **kwargs -- Additional arguments for the Popen constructor
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Exit status of the command
|
||||||
|
|
||||||
|
"""
|
||||||
|
for a in ['stdout', 'stderr']:
|
||||||
|
if a in kwargs:
|
||||||
|
raise ValueError('%s argument not allowed, '
|
||||||
|
'it will be overridden.' % a)
|
||||||
|
if not callable(callback):
|
||||||
|
raise ValueError('callback argument must be callable')
|
||||||
|
|
||||||
|
# Run the command
|
||||||
|
p = subprocess.Popen(args, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, **kwargs)
|
||||||
|
|
||||||
|
# Wrap and get command output
|
||||||
|
stream = NonBlockingStreamReader(p.stdout)
|
||||||
|
while True:
|
||||||
|
line = stream.readline(True, 0.1)
|
||||||
|
if not line:
|
||||||
|
# Check if process has terminated
|
||||||
|
returncode = p.poll()
|
||||||
|
if returncode is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
callback(line.rstrip())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
stream.close()
|
||||||
|
|
||||||
|
return returncode
|
||||||
|
|
||||||
|
|
||||||
|
# Call multiple commands -----------------------------------------------
|
||||||
|
|
||||||
|
def check_commands(cmds, raise_on_error=False, callback=None,
|
||||||
|
separate_stderr=False, shell=True, **kwargs):
|
||||||
|
"""Run multiple commands with error management
|
||||||
|
|
||||||
|
Run a list of commands and allow to manage how to treat errors either
|
||||||
|
with raise_on_error or callback arguments.
|
||||||
|
|
||||||
|
If callback is provided, it will be called when the command returns
|
||||||
|
a non-zero exit code. The callback must take 3 arguments; the
|
||||||
|
returncode, the command which failed and the command output. The
|
||||||
|
callback should return either False to stop commands execution or True
|
||||||
|
to continue.
|
||||||
|
Otherwise, if raise_on_error is True a CalledProcessError exception will
|
||||||
|
be raised when a command returns a non-zero exit code.
|
||||||
|
|
||||||
|
If callback is provided or raise_on_error is False, all commands will
|
||||||
|
be executed and the number of failed commands will be returned.
|
||||||
|
|
||||||
|
The standard output and error of a failed command can be separated with
|
||||||
|
separate_stderr set to True. In that case, the output argument passed to
|
||||||
|
the callback or the output attribute of the CalledProcessError exception
|
||||||
|
will be a 2-tuple containing stdout and stderr as byte strings.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
- cmds -- List of commands to run
|
||||||
|
- raise_on_error -- True to raise a CalledProcessError on error if
|
||||||
|
no callback is provided
|
||||||
|
- callback -- Method or object to call on command failure
|
||||||
|
- separate_stderr -- True to return command output as a 2-tuple
|
||||||
|
- **kwargs -- Additional arguments for the Popen constructor
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Number of failed commands
|
||||||
|
|
||||||
|
"""
|
||||||
|
for a in ['stdout', 'stderr']:
|
||||||
|
if a in kwargs:
|
||||||
|
raise ValueError('%s argument not allowed, '
|
||||||
|
'it will be overridden.' % a)
|
||||||
|
error = 0
|
||||||
|
|
||||||
|
if callback is None:
|
||||||
|
if raise_on_error:
|
||||||
|
# Raise on command failure
|
||||||
|
def callback(r, c, o):
|
||||||
|
raise CalledProcessError(r, c, o)
|
||||||
|
else:
|
||||||
|
# Continue commands execution
|
||||||
|
callback = lambda r,c,o: True
|
||||||
|
elif not callable(callback):
|
||||||
|
raise ValueError('callback argument must be callable')
|
||||||
|
|
||||||
|
# Manage stderr
|
||||||
|
if separate_stderr:
|
||||||
|
_stderr = subprocess.PIPE
|
||||||
|
_get_output = lambda o,e: (o,e)
|
||||||
|
else:
|
||||||
|
_stderr = subprocess.STDOUT
|
||||||
|
_get_output = lambda o,e: o
|
||||||
|
|
||||||
|
# Iterate over commands
|
||||||
|
for cmd in cmds:
|
||||||
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
||||||
|
stderr=_stderr, shell=shell, **kwargs)
|
||||||
|
output = _get_output(*process.communicate())
|
||||||
|
retcode = process.poll()
|
||||||
|
if retcode:
|
||||||
|
error += 1
|
||||||
|
if not callback(retcode, cmd, output):
|
||||||
|
break
|
||||||
|
return error
|
Loading…
Add table
Reference in a new issue