Skip to content

Commit

Permalink
Extend ExternalCommandFailed to expose command and returncode attributes
Browse files Browse the repository at this point in the history
As suggested by @pauljeff by private e-mail.
Thanks for the suggestion Paul! :-)
  • Loading branch information
xolox committed Oct 17, 2014
1 parent c397f50 commit 7063844
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 22 deletions.
17 changes: 10 additions & 7 deletions README.rst
Expand Up @@ -37,8 +37,10 @@ Traceback (most recent call last):
raise ExternalCommandFailed, msg % (shell.returncode, command)
executor.ExternalCommandFailed: External command failed with exit code 1! (command: false)

If you know a command is likely to exit with a nonzero status code and you
want ``execute()`` to simply return a boolean you can do this:
The exceptions raised by the ``execute()`` functions expose ``command`` and
``returncode`` attributes. If you know a command is likely to exit with a
nonzero status code and you want ``execute()`` to simply return a boolean you
can do this:

>>> execute('false', check=False)
False
Expand Down Expand Up @@ -77,10 +79,10 @@ DEBUG:executor:Executing external command: sudo sh -c 'echo peter-macbook > /etc
Contact
-------

The latest version of ``executor`` is available on PyPi_ and GitHub_ (although
I don't suppose much will change, since it's so simple). For bug reports please
create an issue on GitHub_. If you have questions, suggestions, etc. feel free
to send me an e-mail at `peter@peterodding.com`_.
The latest version of ``executor`` is available on PyPI_ and GitHub_. The
documentation is hosted on `Read the Docs`_. For bug reports please create an
issue on GitHub_. If you have questions, suggestions, etc. feel free to send me
an e-mail at `peter@peterodding.com`_.

License
-------
Expand All @@ -94,5 +96,6 @@ This software is licensed under the `MIT license`_.
.. _GitHub: https://github.com/xolox/python-executor
.. _MIT license: http://en.wikipedia.org/wiki/MIT_License
.. _peter@peterodding.com: peter@peterodding.com
.. _PyPi: https://pypi.python.org/pypi/executor
.. _PyPI: https://pypi.python.org/pypi/executor
.. _Read the Docs: https://executor.readthedocs.org
.. _subprocess: https://docs.python.org/2/library/subprocess.html
25 changes: 16 additions & 9 deletions executor/__init__.py
@@ -1,7 +1,7 @@
# Programmer friendly subprocess wrapper.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: June 7, 2014
# Last Change: October 17, 2014
# URL: https://executor.readthedocs.org

# Standard library modules.
Expand All @@ -11,7 +11,7 @@
import subprocess

# Semi-standard module versioning.
__version__ = '1.3'
__version__ = '1.4'

# Initialize a logger.
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -40,8 +40,9 @@ def execute(*command, **options):
running with ``root`` privileges the command is prefixed
with ``fakeroot``. If ``fakeroot`` is not installed we
fall back to ``sudo``.
:param encoding: In Python 3 the subprocess module expects the type of
``input`` to be bytes. If :py:func:`execute()` is given a
:param encoding: In Python 3 the :py:func:`subprocess.Popen()` function
expects its ``input`` argument to be an instance of
:py:class:`bytes`. If :py:func:`execute()` is given a
string as input it automatically encodes it. The default
encoding is UTF-8. You can change it using this argument
by passing a string containing the name of an encoding.
Expand Down Expand Up @@ -88,8 +89,7 @@ def execute(*command, **options):
input = input.encode(encoding)
stdout, stderr = shell.communicate(input=input)
if options.get('check', True) and shell.returncode != 0:
msg = "External command failed with exit code %s! (command: %s)"
raise ExternalCommandFailed(msg % (shell.returncode, command))
raise ExternalCommandFailed(command, shell.returncode)
if options.get('capture', False):
stdout = stdout.decode(encoding)
stripped = stdout.strip()
Expand All @@ -100,7 +100,7 @@ def execute(*command, **options):

def which(program):
"""
Find the pathname of a program on the executable search path (``$PATH``).
Find the pathname(s) of a program on the executable search path (``$PATH``).
:param program: The name of the program (a string).
:returns: A list of pathnames (strings) with found programs.
Expand All @@ -125,10 +125,17 @@ def which(program):


class ExternalCommandFailed(Exception):

"""
Raised by :py:func:`execute()` when an external command exits with a
nonzero status code.
"""
:ivar command: The command line that was executed (a string).
:ivar returncode: The return code of the external command (an integer).
"""

# vim: ts=4 sw=4
def __init__(self, command, returncode):
self.command = command
self.returncode = returncode
error_message = "External command failed with exit code %s! (command: %s)"
super(ExternalCommandFailed, self).__init__(error_message % (returncode, command))
14 changes: 11 additions & 3 deletions executor/tests.py
@@ -1,7 +1,7 @@
# Automated tests for the `executor' module.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: June 7, 2014
# Last Change: October 17, 2014
# URL: https://executor.readthedocs.org

# Standard library modules.
Expand Down Expand Up @@ -32,6 +32,16 @@ def test_status_code_checking(self):
self.assertTrue(execute('true'))
self.assertFalse(execute('false', check=False))
self.assertRaises(ExternalCommandFailed, execute, 'false')
try:
execute('bash', '-c', 'exit 42')
# Make sure the previous line raised an exception.
self.assertTrue(False)
except Exception as e:
# Make sure the expected type of exception was raised.
self.assertTrue(isinstance(e, ExternalCommandFailed))
# Make sure the exception has the expected properties.
self.assertEqual(e.command, "bash -c 'exit 42'")
self.assertEqual(e.returncode, 42)

def test_subprocess_output(self):
self.assertEqual(execute('echo this is a test', capture=True), 'this is a test')
Expand Down Expand Up @@ -70,5 +80,3 @@ def test_sudo_option(self):
self.assertEqual(execute('stat', '--format=%a', filename, sudo=True, capture=True), '600')
finally:
self.assertTrue(execute('rm', filename, sudo=True))

# vim: ts=4 sw=4 et
4 changes: 1 addition & 3 deletions setup.py
Expand Up @@ -3,7 +3,7 @@
# Setup script for the `executor' package.
#
# Author: Peter Odding <peter@peterodding.com>
# Last Change: May 4, 2014
# Last Change: October 17, 2014
# URL: https://executor.readthedocs.org

import os, sys
Expand Down Expand Up @@ -33,5 +33,3 @@
author_email='peter@peterodding.com',
packages=find_packages(),
test_suite='executor.tests')

# vim: ts=4 sw=4

0 comments on commit 7063844

Please sign in to comment.