diff --git a/.travis.yml b/.travis.yml index 291158a..2a30f4e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,8 @@ matrix: env: TOXENV=flake8 - python: 3.6 env: TOXENV=py3flake8 + - python: 2.6 + env: TOXENV=py26 - python: 2.7 env: TOXENV=py27 - python: 3.4 diff --git a/dev-requirements.txt b/dev-requirements.txt index 738958e..e522d8f 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,5 @@ pytest pytest-cov -sphinx>=1.1 \ No newline at end of file +# Pin an older version of sphinx for Python-2.6 compat. We could also drop building of hte sphinx +# docs in this branch (People can either use the raw rst or build docs on a more recent Python) +sphinx>=1.1,<1.5 diff --git a/distro.py b/distro.py index 3306163..2df122c 100755 --- a/distro.py +++ b/distro.py @@ -34,7 +34,7 @@ import json import shlex import logging -import argparse +import optparse import subprocess @@ -94,6 +94,56 @@ ) +# +# Python 2.6 does not have subprocess.check_output so replicate it here +# +def _my_check_output(*popenargs, **kwargs): + r"""Run command with arguments and return its output as a byte string. + + If the exit code was non-zero it raises a CalledProcessError. The + CalledProcessError object will have the return code in the returncode + attribute and output in the output attribute. + + The arguments are the same as for the Popen constructor. Example: + + >>> check_output(["ls", "-l", "/dev/null"]) + 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' + + The stdout argument is not allowed as it is used internally. + To capture standard error in the result, use stderr=STDOUT. + + >>> check_output(["/bin/sh", "-c", + ... "ls -l non_existent_file ; exit 0"], + ... stderr=STDOUT) + 'ls: non_existent_file: No such file or directory\n' + + This is a backport of Python-2.7's check output to Python-2.6 + """ + if 'stdout' in kwargs: + raise ValueError( + 'stdout argument not allowed, it will be overridden.' + ) + process = subprocess.Popen( + stdout=subprocess.PIPE, *popenargs, **kwargs + ) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + # Deviation from Python-2.7: Python-2.6's CalledProcessError does not + # have an argument for the stdout so simply omit it. + raise subprocess.CalledProcessError(retcode, cmd) + return output + + +try: + _check_output = subprocess.check_output +except AttributeError: + _check_output = _my_check_output + + def linux_distribution(full_distribution_name=True): """ Return information about the current OS distribution as a tuple @@ -548,7 +598,7 @@ def __init__(self, f): self._f = f def __get__(self, obj, owner): - assert obj is not None, 'call {} on an instance'.format(self._fname) + assert obj is not None, 'call {0} on an instance'.format(self._fname) ret = obj.__dict__[self._fname] = self._f(obj) return ret @@ -1009,7 +1059,7 @@ def _lsb_release_info(self): with open(os.devnull, 'w') as devnull: try: cmd = ('lsb_release', '-a') - stdout = subprocess.check_output(cmd, stderr=devnull) + stdout = _check_output(cmd, stderr=devnull) except OSError: # Command not found return {} content = stdout.decode(sys.getfilesystemencoding()).splitlines() @@ -1044,7 +1094,7 @@ def _uname_info(self): with open(os.devnull, 'w') as devnull: try: cmd = ('uname', '-rs') - stdout = subprocess.check_output(cmd, stderr=devnull) + stdout = _check_output(cmd, stderr=devnull) except OSError: return {} content = stdout.decode(sys.getfilesystemencoding()).splitlines() @@ -1194,13 +1244,13 @@ def main(): logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler(sys.stdout)) - parser = argparse.ArgumentParser(description="OS distro info tool") - parser.add_argument( + parser = optparse.OptionParser(description="OS distro info tool") + parser.add_option( '--json', '-j', help="Output in machine readable format", action="store_true") - args = parser.parse_args() + args, opts = parser.parse_args() if args.json: logger.info(json.dumps(info(), indent=4, sort_keys=True)) diff --git a/tests/test_distro.py b/tests/test_distro.py index 5521068..5006c2d 100644 --- a/tests/test_distro.py +++ b/tests/test_distro.py @@ -2060,3 +2060,17 @@ def test_repr(self): assert "LinuxDistribution" in repr_str for attr in MODULE_DISTRO.__dict__.keys(): assert attr + '=' in repr_str + + +# +# For testing 2.6 compat code +# + +@pytest.mark.skipif(not IS_LINUX, reason='Irrelevant on non-linux') +class TestCheckOutput: + def test_stdout_disallowed(self): + with pytest.raises(ValueError) as exc_info: + distro._my_check_output(['echo', 'hello'], stdout=None) + + assert exc_info.value.args[0] == 'stdout argument not allowed,' \ + ' it will be overridden.'