diff --git a/HISTORY.rst b/HISTORY.rst index 7a75703..85dd35a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -6,6 +6,8 @@ ------------------ * Support file system path objects (PEP-519) in arguments +* Change the return object to make it similar to + subprocess.CompletedProcess, introduced with Python 3.5+ 0.9.1 (2019-02-22) ------------------ diff --git a/procrunner/__init__.py b/procrunner/__init__.py index 5eeffc4..eaa74ad 100644 --- a/procrunner/__init__.py +++ b/procrunner/__init__.py @@ -331,6 +331,30 @@ return command +if sys.version_info < (3, 5): + + class _ReturnObjectParent(object): + def check_returncode(self): + if self.returncode: + raise Exception( + "Call %r resulted in non-zero exit code %r" + % (self.args, self.returncode) + ) + + +else: + _ReturnObjectParent = subprocess.CompletedProcess + + +class ReturnObject(dict, _ReturnObjectParent): + def __init__(self, *arg, **kw): + super(ReturnObject, self).__init__(*arg, **kw) + self.args = self["command"] + self.returncode = self["exitcode"] + self.stdout = self["stdout"] + self.stderr = self["stderr"] + + def run( command, timeout=None, diff --git a/tests/test_procrunner.py b/tests/test_procrunner.py index 591fd14..9704c1c 100644 --- a/tests/test_procrunner.py +++ b/tests/test_procrunner.py @@ -254,3 +254,54 @@ callback.assert_not_called() aggregator.flush() callback.assert_called_once_with("morestuff") + + +def test_return_object_semantics(): + ro = procrunner.ReturnObject( + { + "command": mock.sentinel.command, + "exitcode": 0, + "stdout": mock.sentinel.stdout, + "stderr": mock.sentinel.stderr, + } + ) + assert ro["command"] == mock.sentinel.command + assert ro.args == mock.sentinel.command + assert ro["exitcode"] == 0 + assert ro.returncode == 0 + assert ro["stdout"] == mock.sentinel.stdout + assert ro.stdout == mock.sentinel.stdout + assert ro["stderr"] == mock.sentinel.stderr + assert ro.stderr == mock.sentinel.stderr + + with pytest.raises(KeyError): + ro["unknownkey"] + ro.update({"unknownkey": mock.sentinel.key}) + assert ro["unknownkey"] == mock.sentinel.key + + +def test_return_object_check_function_passes_on_success(): + ro = procrunner.ReturnObject( + { + "command": mock.sentinel.command, + "exitcode": 0, + "stdout": mock.sentinel.stdout, + "stderr": mock.sentinel.stderr, + } + ) + ro.check_returncode() + + +def test_return_object_check_function_raises_on_error(): + ro = procrunner.ReturnObject( + { + "command": mock.sentinel.command, + "exitcode": 1, + "stdout": mock.sentinel.stdout, + "stderr": mock.sentinel.stderr, + } + ) + with pytest.raises(Exception) as e: + ro.check_returncode() + assert repr(mock.sentinel.command) in str(e.value) + assert "1" in str(e.value)