From 12e05563fce19803bd8b146a57505a7120150bb4 2016-06-30 09:55:48 From: Thomas Kluyver Date: 2016-06-30 09:55:48 Subject: [PATCH] Handle bad __all__ for module completions Closes gh-9678 --- diff --git a/IPython/core/completerlib.py b/IPython/core/completerlib.py index 57dd2b5..3fbc7e6 100644 --- a/IPython/core/completerlib.py +++ b/IPython/core/completerlib.py @@ -153,7 +153,6 @@ def is_importable(module, attr, only_modules): else: return not(attr[:2] == '__' and attr[-2:] == '__') - def try_import(mod, only_modules=False): try: m = __import__(mod) @@ -173,9 +172,8 @@ def try_import(mod, only_modules=False): completions.extend(getattr(m, '__all__', [])) if m_is_init: completions.extend(module_list(os.path.dirname(m.__file__))) - completions = set(completions) - if '__init__' in completions: - completions.remove('__init__') + completions = {c for c in completions if isinstance(c, string_types)} + completions.discard('__init__') return list(completions) diff --git a/IPython/core/tests/bad_all.py b/IPython/core/tests/bad_all.py new file mode 100644 index 0000000..a7716ab --- /dev/null +++ b/IPython/core/tests/bad_all.py @@ -0,0 +1,14 @@ +"""Module with bad __all__ + +To test https://github.com/ipython/ipython/issues/9678 +""" + +def evil(): + pass + +def puppies(): + pass + +__all__ = [evil, # Bad + 'puppies', # Good + ] diff --git a/IPython/core/tests/test_completerlib.py b/IPython/core/tests/test_completerlib.py index 9cc4609..71a6cd2 100644 --- a/IPython/core/tests/test_completerlib.py +++ b/IPython/core/tests/test_completerlib.py @@ -145,3 +145,19 @@ def test_import_invalid_module(): nt.assert_equal(intersection, set()) assert valid_module_names.issubset(s), valid_module_names.intersection(s) + + +def test_bad_module_all(): + """Test module with invalid __all__ + + https://github.com/ipython/ipython/issues/9678 + """ + testsdir = os.path.dirname(__file__) + sys.path.insert(0, testsdir) + try: + results = module_completion('from bad_all import ') + nt.assert_in('puppies', results) + for r in results: + nt.assert_is_instance(r, py3compat.string_types) + finally: + sys.path.remove(testsdir)