Source code for thoth.analyzer.command
#!/usr/bin/env python3
# thoth-analyzer
# Copyright(C) 2018, 2019, 2020 Fridolin Pokorny
#
# This program is free software: you can redistribute it and / or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Handling invoking commands of external programs in a sane way."""
import json
import logging
import delegator
_LOG = logging.getLogger(__name__)
[docs]class CommandResult(object):
"""Representation of result of a command invocation."""
def __init__(self, command: delegator.Command, is_json: bool = False):
"""Initialiaztion of a command result wrapper for delegator."""
self.command = command
self.is_json = is_json
self._stdout = None
@property
def stdout(self):
"""Return standard output from invocation."""
if self._stdout is None:
if self.is_json:
self._stdout = json.loads(self.command.out)
else:
self._stdout = self.command.out
return self._stdout
@property
def stderr(self):
"""Return standard error output from invocation."""
return self.command.err
@property
def return_code(self):
"""Process return code."""
return self.command.return_code
@property
def timeout(self):
"""Timeout that was given to the invoked process to finish."""
return self.command.timeout
[docs] def to_dict(self):
"""Represent command result as a dict."""
return {
"stdout": self.stdout,
"stderr": self.stderr,
"return_code": self.return_code,
"command": self.command.cmd,
"timeout": self.timeout,
}
[docs]class CommandError(RuntimeError, CommandResult):
"""Exception raised on error when calling commands.
Note that this class inherits also from CommandResult, so you can directly
access to_dict() or other defined methods.
"""
def __init__(self, *args, command: delegator.Command, **command_result_kwargs):
"""Store information about command error."""
RuntimeError.__init__(self, *args)
CommandResult.__init__(self, command=command, **command_result_kwargs)
@property
def stdout(self):
"""Return standard output from invocation.
Override implementation for errors, not all tools product JSON or
errors so try to avoid parsing JSON implicitly.
"""
return self.command.out
[docs]def run_command(cmd, timeout=60, is_json=False, env=None, raise_on_error=True):
"""Run the given command, block until it finishes."""
_LOG.debug("Running command %r", cmd)
command = delegator.run(cmd, block=True, timeout=timeout, env=env)
if command.return_code != 0 and raise_on_error:
error_msg = "Command exited with non-zero status code ({}): {}".format(
command.return_code, command.err
)
_LOG.debug(error_msg)
raise CommandError(error_msg, command=command, is_json=is_json)
return CommandResult(command, is_json=is_json)