Repository: numpy/numpy

Issue:
MAINT: exec_command() cleanup
With the impending demise of `exec_command()` related to #11824, there will soon only be unit tests that call this function in our code base even though it will no longer be used.

The unit tests are in `numpy/distutils/tests/test_exec_command.py` -- will we want to remove the tests that specifically call `exec_command()`?  I assume we won't want to unit test the standard library stuff we've replaced `exec_command()` with.

Also, do we need a deprecation marker of some sort on `exec_command()` before removing it? I'm not sure how public-facing / used it is; maybe I need to check that deprecation policy NEP again!


Discussion:
Hmm, I don't see it in scipy or the `__all__` lists. It might make sense to deprecate it just to be safe, but it is probably not needed. @rgommers Thoughts?
ping

This came up again in https://github.com/numpy/numpy/pull/12849

The associated unit tests may be a bit more fragile now--we could remove the unit tests separately from the hard removal of the function itself I suppose.
I think the best way to go is deprecate `exec_command` while keeping the tests, and then remove both at the same time. The tests will need fixing to ignore the deprecation warning.

Resolving patch:
diff --git a/numpy/distutils/exec_command.py b/numpy/distutils/exec_command.py
--- a/numpy/distutils/exec_command.py
+++ b/numpy/distutils/exec_command.py
@@ -57,6 +57,7 @@
 import sys
 import subprocess
 import locale
+import warnings
 
 from numpy.distutils.misc_util import is_sequence, make_temp_file
 from numpy.distutils import log
@@ -105,6 +106,9 @@ def forward_bytes_to_stdout(val):
 
 
 def temp_file_name():
+    # 2019-01-30, 1.17
+    warnings.warn('temp_file_name is deprecated since NumPy v1.17, use '
+                  'tempfile.mkstemp instead', DeprecationWarning, stacklevel=1)
     fo, name = make_temp_file()
     fo.close()
     return name
@@ -179,24 +183,14 @@ def _update_environment( **env ):
     for name, value in env.items():
         os.environ[name] = value or ''
 
-def _supports_fileno(stream):
-    """
-    Returns True if 'stream' supports the file descriptor and allows fileno().
-    """
-    if hasattr(stream, 'fileno'):
-        try:
-            stream.fileno()
-            return True
-        except IOError:
-            return False
-    else:
-        return False
-
 def exec_command(command, execute_in='', use_shell=None, use_tee=None,
                  _with_python = 1, **env ):
     """
     Return (status,output) of executed command.
 
+    .. deprecated:: 1.17
+        Use subprocess.Popen instead
+
     Parameters
     ----------
     command : str
@@ -220,6 +214,9 @@ def exec_command(command, execute_in='', use_shell=None, use_tee=None,
     Wild cards will not work for non-posix systems or when use_shell=0.
 
     """
+    # 2019-01-30, 1.17
+    warnings.warn('exec_command is deprecated since NumPy v1.17, use '
+                  'subprocess.Popen instead', DeprecationWarning, stacklevel=1)
     log.debug('exec_command(%r,%s)' % (command,\
          ','.join(['%s=%r'%kv for kv in env.items()])))
 


Test patch:
diff --git a/numpy/distutils/tests/test_exec_command.py b/numpy/distutils/tests/test_exec_command.py
--- a/numpy/distutils/tests/test_exec_command.py
+++ b/numpy/distutils/tests/test_exec_command.py
@@ -6,7 +6,7 @@
 
 from numpy.distutils import exec_command
 from numpy.distutils.exec_command import get_pythonexe
-from numpy.testing import tempdir, assert_
+from numpy.testing import tempdir, assert_, assert_warns
 
 # In python 3 stdout, stderr are text (unicode compliant) devices, so to
 # emulate them import StringIO from the io module.
@@ -71,27 +71,31 @@ def test_exec_command_stdout():
     # Test posix version:
     with redirect_stdout(StringIO()):
         with redirect_stderr(TemporaryFile()):
-            exec_command.exec_command("cd '.'")
+            with assert_warns(DeprecationWarning):
+                exec_command.exec_command("cd '.'")
 
     if os.name == 'posix':
         # Test general (non-posix) version:
         with emulate_nonposix():
             with redirect_stdout(StringIO()):
                 with redirect_stderr(TemporaryFile()):
-                    exec_command.exec_command("cd '.'")
+                    with assert_warns(DeprecationWarning):
+                        exec_command.exec_command("cd '.'")
 
 def test_exec_command_stderr():
     # Test posix version:
     with redirect_stdout(TemporaryFile(mode='w+')):
         with redirect_stderr(StringIO()):
-            exec_command.exec_command("cd '.'")
+            with assert_warns(DeprecationWarning):
+                exec_command.exec_command("cd '.'")
 
     if os.name == 'posix':
         # Test general (non-posix) version:
         with emulate_nonposix():
             with redirect_stdout(TemporaryFile()):
                 with redirect_stderr(StringIO()):
-                    exec_command.exec_command("cd '.'")
+                    with assert_warns(DeprecationWarning):
+                        exec_command.exec_command("cd '.'")
 
 
 class TestExecCommand(object):
@@ -205,11 +209,12 @@ def check_execute_in(self, **kws):
     def test_basic(self):
         with redirect_stdout(StringIO()):
             with redirect_stderr(StringIO()):
-                if os.name == "posix":
-                    self.check_posix(use_tee=0)
-                    self.check_posix(use_tee=1)
-                elif os.name == "nt":
-                    self.check_nt(use_tee=0)
-                    self.check_nt(use_tee=1)
-                self.check_execute_in(use_tee=0)
-                self.check_execute_in(use_tee=1)
+                with assert_warns(DeprecationWarning):
+                    if os.name == "posix":
+                        self.check_posix(use_tee=0)
+                        self.check_posix(use_tee=1)
+                    elif os.name == "nt":
+                        self.check_nt(use_tee=0)
+                        self.check_nt(use_tee=1)
+                    self.check_execute_in(use_tee=0)
+                    self.check_execute_in(use_tee=1)