Skip to content

Use subprocess32 on python 2.7 #6576

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 16 additions & 46 deletions lib/matplotlib/compat/subprocess.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""
A replacement wrapper around the subprocess module, with a number of
work-arounds:
- Provides the check_output function (which subprocess only provides from Python
2.7 onwards).
- Provides a stub implementation of subprocess members on Google App Engine
(which are missing in subprocess).
- Use subprocess32, backport from python 3.2 on Linux/Mac work-around for
https://github.com/matplotlib/matplotlib/issues/5314

Instead of importing subprocess, other modules should use this as follows:

Expand All @@ -15,8 +15,16 @@

from __future__ import absolute_import # Required to import subprocess
from __future__ import print_function

import subprocess
import os
import sys
if os.name == 'posix' and sys.version_info[:2] < (3, 2):
# work around for https://github.com/matplotlib/matplotlib/issues/5314
try:
import subprocess32 as subprocess
except ImportError:
import subprocess
else:
import subprocess

__all__ = ['Popen', 'PIPE', 'STDOUT', 'check_output', 'CalledProcessError']

Expand All @@ -27,55 +35,17 @@
PIPE = subprocess.PIPE
STDOUT = subprocess.STDOUT
CalledProcessError = subprocess.CalledProcessError
check_output = subprocess.check_output
else:
# In restricted environments (such as Google App Engine), these are
# non-existent. Replace them with dummy versions that always raise OSError.
def Popen(*args, **kwargs):
raise OSError("subprocess.Popen is not supported")

def check_output(*args, **kwargs):
raise OSError("subprocess.check_output is not supported")
PIPE = -1
STDOUT = -2
# There is no need to catch CalledProcessError. These stubs cannot raise
# it. None in an except clause will simply not match any exceptions.
CalledProcessError = None


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 = Popen(stdout=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


# python2.7's subprocess provides a check_output method
if hasattr(subprocess, 'check_output'):
check_output = subprocess.check_output
else:
check_output = _check_output
1 change: 0 additions & 1 deletion lib/matplotlib/tests/test_coding_standards.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ def test_pep8_conformance_installed_files():
'tests/test_subplots.py',
'tests/test_tightlayout.py',
'tests/test_triangulation.py',
'compat/subprocess.py',
'backends/__init__.py',
'backends/backend_agg.py',
'backends/backend_cairo.py',
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
setupext.Six(),
setupext.Dateutil(),
setupext.FuncTools32(),
setupext.Subprocess32(),
setupext.Pytz(),
setupext.Cycler(),
setupext.Tornado(),
Expand Down
28 changes: 26 additions & 2 deletions setupext.py
Original file line number Diff line number Diff line change
Expand Up @@ -1386,8 +1386,8 @@ def check(self):
import functools32
except ImportError:
return (
"functools32 was not found. It is required for for"
"python versions prior to 3.2")
"functools32 was not found. It is required for"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you didn't mean to change this one and were looking for the double for in the subprocess32 package, but since you're here, it should say Python.

"Python versions prior to 3.2")

return "using functools32"
else:
Expand All @@ -1400,6 +1400,30 @@ def get_install_requires(self):
return []


class Subprocess32(SetupPackage):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does deriving from SetupPackage make it optional (as it states in the message below)?

name = "subprocess32"

def check(self):
if sys.version_info[:2] < (3, 2):
try:
import subprocess32
except ImportError:
return (
"subprocess32 was not found. It used "
" for Python versions prior to 3.2 to improves"
" functionality on Linux and OSX")

return "using subprocess32"
else:
return "Not required"

def get_install_requires(self):
if sys.version_info[:2] < (3, 2) and os.name == 'posix':
return ['subprocess32']
else:
return []


class Tornado(OptionalPackage):
name = "tornado"

Expand Down