Repository: ipython/ipython

Issue:
Missing ship numpy testing decorator
```
Traceback (most recent call last):
  File "/Users/mbussonnier/dev/cpython/test/bin/iptest", line 6, in <module>
    from IPython.testing.iptestcontroller import main
  File "/Users/mbussonnier/dev/cpython/test/lib/python3.8/site-packages/IPython/testing/iptestcontroller.py", line 23, in <module>
    from .iptest import (
  File "/Users/mbussonnier/dev/cpython/test/lib/python3.8/site-packages/IPython/testing/iptest.py", line 40, in <module>
    from IPython.external.decorators import KnownFailure, dec
ImportError: cannot import name 'dec' from 'IPython.external.decorators' (/Users/mbussonnier/dev/cpython/test/lib/python3.8/site-packages/IPython/external/decorators/__init__.py)
```

Seem like `dec` is not define ini out `_decorator.py` 


Appologies for shortness boarding a plane


Discussion:
I can confirm this for Python 3.7:
```
/sw/bin/python3.7 -B IPython/testing/iptest.py IPython
Traceback (most recent call last):
  File "IPython/testing/iptest.py", line 40, in <module>
    from IPython.external.decorators import KnownFailure, dec
ImportError: cannot import name 'dec' from 'IPython.external.decorators' (/scratch.noindex/fink.build/ipython-py37-7.5.0-1/ipython-7.5.0/build/lib/IPython/external/decorators/__init__.py)
```
also using the decorators fails with
```
Python 3.7.3 (default, May  5 2019, 04:25:55) 
[Clang 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from IPython.testing import decorators as dec
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/sw/lib/python3.7/site-packages/IPython/testing/decorators.py", line 336, in <module>
    skip_known_failure = dec.knownfailureif(True,'This test is known to fail')
NameError: name 'dec' is not defined
```
installing `numpy` resolves this, making it a dependency for the test suite as well as for using `IPython.testing` by outside applications.
Seems to be due to a252070 and related commits, which try to import `dec` from `numpy.testing` and on `ImportError` import the rest from `._decorators`, but leaving `dec` undefined.

Adding this line to [`external/decorators/__init__.py`](https://github.com/ipython/ipython/blob/master/IPython/external/decorators/__init__.py):
```
except ImportError:
    from ._decorators import *
    from ...testing import decorators as dec
```
enabled `from IPython.testing import decorators as dec` to work, but not `from IPython.external.decorators import KnownFailure, dec` - unless the former import was called first. I.e. `iptest.py` still fails...

Resolving patch:
diff --git a/IPython/external/decorators/__init__.py b/IPython/external/decorators/__init__.py
--- a/IPython/external/decorators/__init__.py
+++ b/IPython/external/decorators/__init__.py
@@ -1,9 +1,7 @@
 try:
-    from numpy.testing import *
-    from numpy.testing import dec
-    from numpy.testing.noseclasses import KnownFailure
+    from numpy.testing.noseclasses import KnownFailure, knownfailureif
 except ImportError:
-    from ._decorators import *
+    from ._decorators import knownfailureif
     try:
         from ._numpy_testing_noseclasses import KnownFailure
     except ImportError:


Test patch:
diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py
--- a/IPython/core/tests/test_completer.py
+++ b/IPython/core/tests/test_completer.py
@@ -183,7 +183,7 @@ def test_forward_unicode_completion():
     nt.assert_equal(matches[0], 'Ⅴ')
 
 @nt.nottest # now we have a completion for \jmath
-@decorators.dec.knownfailureif(sys.platform == 'win32', 'Fails if there is a C:\\j... path')
+@decorators.knownfailureif(sys.platform == 'win32', 'Fails if there is a C:\\j... path')
 def test_no_ascii_back_completion():
     ip = get_ipython()
     with TemporaryWorkingDirectory():  # Avoid any filename completions
@@ -234,7 +234,7 @@ def test_has_open_quotes4():
         nt.assert_false(completer.has_open_quotes(s))
 
 
-@decorators.dec.knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows")
+@decorators.knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows")
 def test_abspath_file_completions():
     ip = get_ipython()
     with TemporaryDirectory() as tmpdir:
diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py
--- a/IPython/core/tests/test_run.py
+++ b/IPython/core/tests/test_run.py
@@ -538,7 +538,7 @@ def test_run_tb():
         nt.assert_in("RuntimeError", out)
         nt.assert_equal(out.count("---->"), 3)
 
-@dec.dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
+@dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
 def test_script_tb():
     """Test traceback offset in `ipython script.py`"""
     with TemporaryDirectory() as td:
diff --git a/IPython/lib/tests/test_display.py b/IPython/lib/tests/test_display.py
--- a/IPython/lib/tests/test_display.py
+++ b/IPython/lib/tests/test_display.py
@@ -26,11 +26,17 @@
 
 # Third-party imports
 import nose.tools as nt
-import numpy
+
+try:
+    import numpy
+except ImportError:
+    pass
 
 # Our own imports
 from IPython.lib import display
 
+from IPython.testing.decorators import skipif_not_numpy
+
 #-----------------------------------------------------------------------------
 # Classes and functions
 #-----------------------------------------------------------------------------
@@ -188,19 +194,24 @@ def test_audio_from_file():
     display.Audio(filename=path)
 
 class TestAudioDataWithNumpy(TestCase):
+
+    @skipif_not_numpy
     def test_audio_from_numpy_array(self):
         test_tone = get_test_tone()
         audio = display.Audio(test_tone, rate=44100)
         nt.assert_equal(len(read_wav(audio.data)), len(test_tone))
 
+    @skipif_not_numpy
     def test_audio_from_list(self):
         test_tone = get_test_tone()
         audio = display.Audio(list(test_tone), rate=44100)
         nt.assert_equal(len(read_wav(audio.data)), len(test_tone))
 
+    @skipif_not_numpy
     def test_audio_from_numpy_array_without_rate_raises(self):
         nt.assert_raises(ValueError, display.Audio, get_test_tone())
 
+    @skipif_not_numpy
     def test_audio_data_normalization(self):
         expected_max_value = numpy.iinfo(numpy.int16).max
         for scale in [1, 0.5, 2]:
@@ -208,6 +219,7 @@ def test_audio_data_normalization(self):
             actual_max_value = numpy.max(numpy.abs(read_wav(audio.data)))
             nt.assert_equal(actual_max_value, expected_max_value)
 
+    @skipif_not_numpy
     def test_audio_data_without_normalization(self):
         max_int16 = numpy.iinfo(numpy.int16).max
         for scale in [1, 0.5, 0.2]:
@@ -233,6 +245,7 @@ def simulate_numpy_not_installed():
 class TestAudioDataWithoutNumpy(TestAudioDataWithNumpy):
     # All tests from `TestAudioDataWithNumpy` are inherited.
 
+    @skipif_not_numpy
     def test_audio_raises_for_nested_list(self):
         stereo_signal = [list(get_test_tone())] * 2
         nt.assert_raises(
diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py
--- a/IPython/testing/decorators.py
+++ b/IPython/testing/decorators.py
@@ -47,7 +47,7 @@
 # Grab the numpy-specific decorators which we keep in a file that we
 # occasionally update from upstream: decorators.py is a copy of
 # numpy.testing.decorators, we expose all of it here.
-from IPython.external.decorators import *
+from IPython.external.decorators import knownfailureif
 
 #-----------------------------------------------------------------------------
 # Classes and functions
@@ -333,7 +333,7 @@ def skip_file_no_x11(name):
 
 skipif_not_sympy = skip_without('sympy')
 
-skip_known_failure = dec.knownfailureif(True,'This test is known to fail')
+skip_known_failure = knownfailureif(True,'This test is known to fail')
 
 # A null 'decorator', useful to make more readable code that needs to pick
 # between different decorators based on OS or other conditions
diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py
--- a/IPython/testing/iptest.py
+++ b/IPython/testing/iptest.py
@@ -37,7 +37,7 @@
 from IPython.utils.py3compat import decode
 from IPython.utils.importstring import import_item
 from IPython.testing.plugin.ipdoctest import IPythonDoctest
-from IPython.external.decorators import KnownFailure, dec
+from IPython.external.decorators import KnownFailure, knownfailureif
 
 pjoin = path.join