From 7a76065d427581e8857e22a9ce2cd08f94490090 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Fri, 29 Dec 2017 12:20:57 -0500 Subject: [PATCH 1/8] Regain Python 2.6 support --- distro.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/distro.py b/distro.py index 80a90367..8d231c21 100755 --- a/distro.py +++ b/distro.py @@ -546,7 +546,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 @@ -982,6 +982,14 @@ def _parse_os_release_content(lines): pass return props + def check_output(*args, **kwargs): + """Run command with arguments and return its output as a byte string. + + Same as subprocess.check_output(), but works before Python 2.7.""" + + p = subprocess.Popen(stdout=subprocess.PIPE, *args, **kwargs) + return p.communicate()[0].strip() + @cached_property def _lsb_release_info(self): """ @@ -995,7 +1003,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() From 570e7068c6d4d8ef5a45654e9ec998f547c55b4a Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Fri, 29 Dec 2017 12:31:45 -0500 Subject: [PATCH 2/8] Flake8 fix --- distro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distro.py b/distro.py index 8d231c21..f1203752 100755 --- a/distro.py +++ b/distro.py @@ -1003,7 +1003,7 @@ def _lsb_release_info(self): with open(os.devnull, 'w') as devnull: try: cmd = ('lsb_release', '-a') - stdout = check_output(cmd, stderr=devnull) + stdout = self.check_output(cmd, stderr=devnull) except OSError: # Command not found return {} content = stdout.decode(sys.getfilesystemencoding()).splitlines() From 6aeb4a16abf8f2a29fc60bc644c60855e5b526f5 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Fri, 29 Dec 2017 12:42:55 -0500 Subject: [PATCH 3/8] Don't use auxiliary function --- distro.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/distro.py b/distro.py index f1203752..bf13f430 100755 --- a/distro.py +++ b/distro.py @@ -982,14 +982,6 @@ def _parse_os_release_content(lines): pass return props - def check_output(*args, **kwargs): - """Run command with arguments and return its output as a byte string. - - Same as subprocess.check_output(), but works before Python 2.7.""" - - p = subprocess.Popen(stdout=subprocess.PIPE, *args, **kwargs) - return p.communicate()[0].strip() - @cached_property def _lsb_release_info(self): """ @@ -1003,7 +995,7 @@ def _lsb_release_info(self): with open(os.devnull, 'w') as devnull: try: cmd = ('lsb_release', '-a') - stdout = self.check_output(cmd, stderr=devnull) + stdout = subprocess.Popen(cmd, stderr=devnull).communicate()[0] except OSError: # Command not found return {} content = stdout.decode(sys.getfilesystemencoding()).splitlines() From 690cc6740d07a1ead766f00eaf32866904fe18a2 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Fri, 29 Dec 2017 13:04:52 -0500 Subject: [PATCH 4/8] Steal check_output code from Python 2.7 --- distro.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/distro.py b/distro.py index bf13f430..149cd038 100755 --- a/distro.py +++ b/distro.py @@ -982,6 +982,42 @@ def _parse_os_release_content(lines): pass return props + def _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' + """ + 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] + raise subprocess.CalledProcessError(retcode, cmd, output=output) + return output + @cached_property def _lsb_release_info(self): """ @@ -995,7 +1031,7 @@ def _lsb_release_info(self): with open(os.devnull, 'w') as devnull: try: cmd = ('lsb_release', '-a') - stdout = subprocess.Popen(cmd, stderr=devnull).communicate()[0] + stdout = self._check_output(cmd, stderr=devnull) except OSError: # Command not found return {} content = stdout.decode(sys.getfilesystemencoding()).splitlines() From 773ba48b5edbda5fcaadd22fbd9c411d3e1b4854 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 13 Dec 2018 09:29:26 -0800 Subject: [PATCH 5/8] Fix bundled check_output for 2.6 * Port the 2.7 version of check_output that we're bundling for 2.6 * Only use our check_output when running on a Python which does not have subprocess.check_output * Pull check_output code to the toplevel because it isn't conceptually part of LinuxDistribution --- distro.py | 90 +++++++++++++++++++++++++------------------- tests/test_distro.py | 14 +++++++ 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/distro.py b/distro.py index 149cd038..d1080ab8 100755 --- a/distro.py +++ b/distro.py @@ -92,6 +92,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 @@ -982,42 +1032,6 @@ def _parse_os_release_content(lines): pass return props - def _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' - """ - 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] - raise subprocess.CalledProcessError(retcode, cmd, output=output) - return output - @cached_property def _lsb_release_info(self): """ @@ -1031,7 +1045,7 @@ def _lsb_release_info(self): with open(os.devnull, 'w') as devnull: try: cmd = ('lsb_release', '-a') - stdout = self._check_output(cmd, stderr=devnull) + stdout = _check_output(cmd, stderr=devnull) except OSError: # Command not found return {} content = stdout.decode(sys.getfilesystemencoding()).splitlines() @@ -1066,7 +1080,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() diff --git a/tests/test_distro.py b/tests/test_distro.py index f5ac2b2b..80d1526c 100644 --- a/tests/test_distro.py +++ b/tests/test_distro.py @@ -1978,3 +1978,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.' From fc541486bf5c585ea899ec63cc2512b0ba125c07 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 13 Dec 2018 13:25:41 -0800 Subject: [PATCH 6/8] Use optparse instead of argparse to parse command line arguments (py2.6 compat) --- distro.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/distro.py b/distro.py index d1080ab8..ecb2eeb4 100755 --- a/distro.py +++ b/distro.py @@ -34,7 +34,7 @@ import json import shlex import logging -import argparse +import optparse import subprocess @@ -1225,13 +1225,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)) From 3a8e02b84399e6df2eedb11ce97c565f01b3846a Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 13 Dec 2018 09:33:25 -0800 Subject: [PATCH 7/8] Update travis to test python2.6 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 291158a1..2a30f4e9 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 From 103e388ee3d3a7d2c2bb54180cff2ecaa48cd0a1 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 13 Dec 2018 10:58:18 -0800 Subject: [PATCH 8/8] Update sphinx requirement to build on Python-2.6 --- dev-requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 738958e0..e522d8f1 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