##// END OF EJS Templates
Implemented fix for https://github.com/ipython/ipython/issues/1107 by checking that the module list only returns importable modules.
Ross Jones -
Show More
@@ -1,321 +1,326 b''
1 """Implementations for various useful completers.
1 """Implementations for various useful completers.
2
2
3 These are all loaded by default by IPython.
3 These are all loaded by default by IPython.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2011 The IPython Development Team.
6 # Copyright (C) 2010-2011 The IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the BSD License.
8 # Distributed under the terms of the BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib imports
18 # Stdlib imports
19 import glob
19 import glob
20 import inspect
20 import inspect
21 import os
21 import os
22 import re
22 import re
23 import sys
23 import sys
24
24
25 # Third-party imports
25 # Third-party imports
26 from time import time
26 from time import time
27 from zipimport import zipimporter
27 from zipimport import zipimporter
28
28
29 # Our own imports
29 # Our own imports
30 from IPython.core.completer import expand_user, compress_user
30 from IPython.core.completer import expand_user, compress_user
31 from IPython.core.error import TryNext
31 from IPython.core.error import TryNext
32 from IPython.utils import py3compat
32 from IPython.utils import py3compat
33 from IPython.utils._process_common import arg_split
33 from IPython.utils._process_common import arg_split
34
34
35 # FIXME: this should be pulled in with the right call via the component system
35 # FIXME: this should be pulled in with the right call via the component system
36 from IPython.core.ipapi import get as get_ipython
36 from IPython.core.ipapi import get as get_ipython
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Globals and constants
39 # Globals and constants
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 # Time in seconds after which the rootmodules will be stored permanently in the
42 # Time in seconds after which the rootmodules will be stored permanently in the
43 # ipython ip.db database (kept in the user's .ipython dir).
43 # ipython ip.db database (kept in the user's .ipython dir).
44 TIMEOUT_STORAGE = 2
44 TIMEOUT_STORAGE = 2
45
45
46 # Time in seconds after which we give up
46 # Time in seconds after which we give up
47 TIMEOUT_GIVEUP = 20
47 TIMEOUT_GIVEUP = 20
48
48
49 # Regular expression for the python import statement
49 # Regular expression for the python import statement
50 import_re = re.compile(r'.*(\.so|\.py[cod]?)$')
50 import_re = re.compile(r'.*(\.so|\.py[cod]?)$')
51
51
52 # RE for the ipython %run command (python + ipython scripts)
52 # RE for the ipython %run command (python + ipython scripts)
53 magic_run_re = re.compile(r'.*(\.ipy|\.py[w]?)$')
53 magic_run_re = re.compile(r'.*(\.ipy|\.py[w]?)$')
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Local utilities
56 # Local utilities
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 def module_list(path):
59 def module_list(path):
60 """
60 """
61 Return the list containing the names of the modules available in the given
61 Return the list containing the names of the modules available in the given
62 folder.
62 folder.
63 """
63 """
64
64
65 if os.path.isdir(path):
65 if os.path.isdir(path):
66 folder_list = os.listdir(path)
66 folder_list = os.listdir(path)
67 elif path.endswith('.egg'):
67 elif path.endswith('.egg'):
68 try:
68 try:
69 folder_list = [f for f in zipimporter(path)._files]
69 folder_list = [f for f in zipimporter(path)._files]
70 except:
70 except:
71 folder_list = []
71 folder_list = []
72 else:
72 else:
73 folder_list = []
73 folder_list = []
74
74
75 if not folder_list:
75 if not folder_list:
76 return []
76 return []
77
77
78 # A few local constants to be used in loops below
78 # A few local constants to be used in loops below
79 isfile = os.path.isfile
79 isfile = os.path.isfile
80 pjoin = os.path.join
80 pjoin = os.path.join
81 basename = os.path.basename
81 basename = os.path.basename
82
82
83 def is_importable_file(path):
84 """Returns True if the provided path is a valid importable module"""
85 name, extension = os.path.splitext( path )
86 return import_re.match(path) and py3compat.isidentifier(name)
87
83 # Now find actual path matches for packages or modules
88 # Now find actual path matches for packages or modules
84 folder_list = [p for p in folder_list
89 folder_list = [p for p in folder_list
85 if isfile(pjoin(path, p,'__init__.py'))
90 if isfile(pjoin(path, p,'__init__.py'))
86 or import_re.match(p) ]
91 or is_importable_file(p) ]
87
92
88 return [basename(p).split('.')[0] for p in folder_list]
93 return [basename(p).split('.')[0] for p in folder_list]
89
94
90 def get_root_modules():
95 def get_root_modules():
91 """
96 """
92 Returns a list containing the names of all the modules available in the
97 Returns a list containing the names of all the modules available in the
93 folders of the pythonpath.
98 folders of the pythonpath.
94 """
99 """
95 ip = get_ipython()
100 ip = get_ipython()
96
101
97 if 'rootmodules' in ip.db:
102 if 'rootmodules' in ip.db:
98 return ip.db['rootmodules']
103 return ip.db['rootmodules']
99
104
100 t = time()
105 t = time()
101 store = False
106 store = False
102 modules = list(sys.builtin_module_names)
107 modules = list(sys.builtin_module_names)
103 for path in sys.path:
108 for path in sys.path:
104 modules += module_list(path)
109 modules += module_list(path)
105 if time() - t >= TIMEOUT_STORAGE and not store:
110 if time() - t >= TIMEOUT_STORAGE and not store:
106 store = True
111 store = True
107 print("\nCaching the list of root modules, please wait!")
112 print("\nCaching the list of root modules, please wait!")
108 print("(This will only be done once - type '%rehashx' to "
113 print("(This will only be done once - type '%rehashx' to "
109 "reset cache!)\n")
114 "reset cache!)\n")
110 sys.stdout.flush()
115 sys.stdout.flush()
111 if time() - t > TIMEOUT_GIVEUP:
116 if time() - t > TIMEOUT_GIVEUP:
112 print("This is taking too long, we give up.\n")
117 print("This is taking too long, we give up.\n")
113 ip.db['rootmodules'] = []
118 ip.db['rootmodules'] = []
114 return []
119 return []
115
120
116 modules = set(modules)
121 modules = set(modules)
117 if '__init__' in modules:
122 if '__init__' in modules:
118 modules.remove('__init__')
123 modules.remove('__init__')
119 modules = list(modules)
124 modules = list(modules)
120 if store:
125 if store:
121 ip.db['rootmodules'] = modules
126 ip.db['rootmodules'] = modules
122 return modules
127 return modules
123
128
124
129
125 def is_importable(module, attr, only_modules):
130 def is_importable(module, attr, only_modules):
126 if only_modules:
131 if only_modules:
127 return inspect.ismodule(getattr(module, attr))
132 return inspect.ismodule(getattr(module, attr))
128 else:
133 else:
129 return not(attr[:2] == '__' and attr[-2:] == '__')
134 return not(attr[:2] == '__' and attr[-2:] == '__')
130
135
131
136
132 def try_import(mod, only_modules=False):
137 def try_import(mod, only_modules=False):
133 try:
138 try:
134 m = __import__(mod)
139 m = __import__(mod)
135 except:
140 except:
136 return []
141 return []
137 mods = mod.split('.')
142 mods = mod.split('.')
138 for module in mods[1:]:
143 for module in mods[1:]:
139 m = getattr(m, module)
144 m = getattr(m, module)
140
145
141 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
146 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
142
147
143 completions = []
148 completions = []
144 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
149 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
145 completions.extend( [attr for attr in dir(m) if
150 completions.extend( [attr for attr in dir(m) if
146 is_importable(m, attr, only_modules)])
151 is_importable(m, attr, only_modules)])
147
152
148 completions.extend(getattr(m, '__all__', []))
153 completions.extend(getattr(m, '__all__', []))
149 if m_is_init:
154 if m_is_init:
150 completions.extend(module_list(os.path.dirname(m.__file__)))
155 completions.extend(module_list(os.path.dirname(m.__file__)))
151 completions = set(completions)
156 completions = set(completions)
152 if '__init__' in completions:
157 if '__init__' in completions:
153 completions.remove('__init__')
158 completions.remove('__init__')
154 return list(completions)
159 return list(completions)
155
160
156
161
157 #-----------------------------------------------------------------------------
162 #-----------------------------------------------------------------------------
158 # Completion-related functions.
163 # Completion-related functions.
159 #-----------------------------------------------------------------------------
164 #-----------------------------------------------------------------------------
160
165
161 def quick_completer(cmd, completions):
166 def quick_completer(cmd, completions):
162 """ Easily create a trivial completer for a command.
167 """ Easily create a trivial completer for a command.
163
168
164 Takes either a list of completions, or all completions in string (that will
169 Takes either a list of completions, or all completions in string (that will
165 be split on whitespace).
170 be split on whitespace).
166
171
167 Example::
172 Example::
168
173
169 [d:\ipython]|1> import ipy_completers
174 [d:\ipython]|1> import ipy_completers
170 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
175 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
171 [d:\ipython]|3> foo b<TAB>
176 [d:\ipython]|3> foo b<TAB>
172 bar baz
177 bar baz
173 [d:\ipython]|3> foo ba
178 [d:\ipython]|3> foo ba
174 """
179 """
175
180
176 if isinstance(completions, basestring):
181 if isinstance(completions, basestring):
177 completions = completions.split()
182 completions = completions.split()
178
183
179 def do_complete(self, event):
184 def do_complete(self, event):
180 return completions
185 return completions
181
186
182 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
187 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
183
188
184 def module_completion(line):
189 def module_completion(line):
185 """
190 """
186 Returns a list containing the completion possibilities for an import line.
191 Returns a list containing the completion possibilities for an import line.
187
192
188 The line looks like this :
193 The line looks like this :
189 'import xml.d'
194 'import xml.d'
190 'from xml.dom import'
195 'from xml.dom import'
191 """
196 """
192
197
193 words = line.split(' ')
198 words = line.split(' ')
194 nwords = len(words)
199 nwords = len(words)
195
200
196 # from whatever <tab> -> 'import '
201 # from whatever <tab> -> 'import '
197 if nwords == 3 and words[0] == 'from':
202 if nwords == 3 and words[0] == 'from':
198 return ['import ']
203 return ['import ']
199
204
200 # 'from xy<tab>' or 'import xy<tab>'
205 # 'from xy<tab>' or 'import xy<tab>'
201 if nwords < 3 and (words[0] in ['import','from']) :
206 if nwords < 3 and (words[0] in ['import','from']) :
202 if nwords == 1:
207 if nwords == 1:
203 return get_root_modules()
208 return get_root_modules()
204 mod = words[1].split('.')
209 mod = words[1].split('.')
205 if len(mod) < 2:
210 if len(mod) < 2:
206 return get_root_modules()
211 return get_root_modules()
207 completion_list = try_import('.'.join(mod[:-1]), True)
212 completion_list = try_import('.'.join(mod[:-1]), True)
208 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
213 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
209
214
210 # 'from xyz import abc<tab>'
215 # 'from xyz import abc<tab>'
211 if nwords >= 3 and words[0] == 'from':
216 if nwords >= 3 and words[0] == 'from':
212 mod = words[1]
217 mod = words[1]
213 return try_import(mod)
218 return try_import(mod)
214
219
215 #-----------------------------------------------------------------------------
220 #-----------------------------------------------------------------------------
216 # Completers
221 # Completers
217 #-----------------------------------------------------------------------------
222 #-----------------------------------------------------------------------------
218 # These all have the func(self, event) signature to be used as custom
223 # These all have the func(self, event) signature to be used as custom
219 # completers
224 # completers
220
225
221 def module_completer(self,event):
226 def module_completer(self,event):
222 """Give completions after user has typed 'import ...' or 'from ...'"""
227 """Give completions after user has typed 'import ...' or 'from ...'"""
223
228
224 # This works in all versions of python. While 2.5 has
229 # This works in all versions of python. While 2.5 has
225 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
230 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
226 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
231 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
227 # of possibly problematic side effects.
232 # of possibly problematic side effects.
228 # This search the folders in the sys.path for available modules.
233 # This search the folders in the sys.path for available modules.
229
234
230 return module_completion(event.line)
235 return module_completion(event.line)
231
236
232 # FIXME: there's a lot of logic common to the run, cd and builtin file
237 # FIXME: there's a lot of logic common to the run, cd and builtin file
233 # completers, that is currently reimplemented in each.
238 # completers, that is currently reimplemented in each.
234
239
235 def magic_run_completer(self, event):
240 def magic_run_completer(self, event):
236 """Complete files that end in .py or .ipy for the %run command.
241 """Complete files that end in .py or .ipy for the %run command.
237 """
242 """
238 comps = arg_split(event.line, strict=False)
243 comps = arg_split(event.line, strict=False)
239 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
244 relpath = (len(comps) > 1 and comps[-1] or '').strip("'\"")
240
245
241 #print("\nev=", event) # dbg
246 #print("\nev=", event) # dbg
242 #print("rp=", relpath) # dbg
247 #print("rp=", relpath) # dbg
243 #print('comps=', comps) # dbg
248 #print('comps=', comps) # dbg
244
249
245 lglob = glob.glob
250 lglob = glob.glob
246 isdir = os.path.isdir
251 isdir = os.path.isdir
247 relpath, tilde_expand, tilde_val = expand_user(relpath)
252 relpath, tilde_expand, tilde_val = expand_user(relpath)
248
253
249 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
254 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
250
255
251 # Find if the user has already typed the first filename, after which we
256 # Find if the user has already typed the first filename, after which we
252 # should complete on all files, since after the first one other files may
257 # should complete on all files, since after the first one other files may
253 # be arguments to the input script.
258 # be arguments to the input script.
254
259
255 if filter(magic_run_re.match, comps):
260 if filter(magic_run_re.match, comps):
256 pys = [f.replace('\\','/') for f in lglob('*')]
261 pys = [f.replace('\\','/') for f in lglob('*')]
257 else:
262 else:
258 pys = [f.replace('\\','/')
263 pys = [f.replace('\\','/')
259 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
264 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
260 lglob(relpath + '*.pyw')]
265 lglob(relpath + '*.pyw')]
261 #print('run comp:', dirs+pys) # dbg
266 #print('run comp:', dirs+pys) # dbg
262 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
267 return [compress_user(p, tilde_expand, tilde_val) for p in dirs+pys]
263
268
264
269
265 def cd_completer(self, event):
270 def cd_completer(self, event):
266 """Completer function for cd, which only returns directories."""
271 """Completer function for cd, which only returns directories."""
267 ip = get_ipython()
272 ip = get_ipython()
268 relpath = event.symbol
273 relpath = event.symbol
269
274
270 #print(event) # dbg
275 #print(event) # dbg
271 if event.line.endswith('-b') or ' -b ' in event.line:
276 if event.line.endswith('-b') or ' -b ' in event.line:
272 # return only bookmark completions
277 # return only bookmark completions
273 bkms = self.db.get('bookmarks', None)
278 bkms = self.db.get('bookmarks', None)
274 if bkms:
279 if bkms:
275 return bkms.keys()
280 return bkms.keys()
276 else:
281 else:
277 return []
282 return []
278
283
279 if event.symbol == '-':
284 if event.symbol == '-':
280 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
285 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
281 # jump in directory history by number
286 # jump in directory history by number
282 fmt = '-%0' + width_dh +'d [%s]'
287 fmt = '-%0' + width_dh +'d [%s]'
283 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
288 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
284 if len(ents) > 1:
289 if len(ents) > 1:
285 return ents
290 return ents
286 return []
291 return []
287
292
288 if event.symbol.startswith('--'):
293 if event.symbol.startswith('--'):
289 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
294 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
290
295
291 # Expand ~ in path and normalize directory separators.
296 # Expand ~ in path and normalize directory separators.
292 relpath, tilde_expand, tilde_val = expand_user(relpath)
297 relpath, tilde_expand, tilde_val = expand_user(relpath)
293 relpath = relpath.replace('\\','/')
298 relpath = relpath.replace('\\','/')
294
299
295 found = []
300 found = []
296 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
301 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
297 if os.path.isdir(f)]:
302 if os.path.isdir(f)]:
298 if ' ' in d:
303 if ' ' in d:
299 # we don't want to deal with any of that, complex code
304 # we don't want to deal with any of that, complex code
300 # for this is elsewhere
305 # for this is elsewhere
301 raise TryNext
306 raise TryNext
302
307
303 found.append(d)
308 found.append(d)
304
309
305 if not found:
310 if not found:
306 if os.path.isdir(relpath):
311 if os.path.isdir(relpath):
307 return [compress_user(relpath, tilde_expand, tilde_val)]
312 return [compress_user(relpath, tilde_expand, tilde_val)]
308
313
309 # if no completions so far, try bookmarks
314 # if no completions so far, try bookmarks
310 bks = self.db.get('bookmarks',{}).iterkeys()
315 bks = self.db.get('bookmarks',{}).iterkeys()
311 bkmatches = [s for s in bks if s.startswith(event.symbol)]
316 bkmatches = [s for s in bks if s.startswith(event.symbol)]
312 if bkmatches:
317 if bkmatches:
313 return bkmatches
318 return bkmatches
314
319
315 raise TryNext
320 raise TryNext
316
321
317 return [compress_user(p, tilde_expand, tilde_val) for p in found]
322 return [compress_user(p, tilde_expand, tilde_val) for p in found]
318
323
319 def reset_completer(self, event):
324 def reset_completer(self, event):
320 "A completer for %reset magic"
325 "A completer for %reset magic"
321 return '-f -s in out array dhist'.split()
326 return '-f -s in out array dhist'.split()
@@ -1,79 +1,80 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for completerlib.
2 """Tests for completerlib.
3
3
4 """
4 """
5 from __future__ import absolute_import
5 from __future__ import absolute_import
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Imports
8 # Imports
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 import os
11 import os
12 import shutil
12 import shutil
13 import sys
13 import sys
14 import tempfile
14 import tempfile
15 import unittest
15 import unittest
16 from os.path import join
16 from os.path import join
17
17
18 import nose.tools as nt
18 import nose.tools as nt
19 from nose import SkipTest
19 from nose import SkipTest
20
20
21 from IPython.core.completerlib import magic_run_completer, module_completion
21 from IPython.core.completerlib import magic_run_completer, module_completion
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23 from IPython.utils.tempdir import TemporaryDirectory
23 from IPython.utils.tempdir import TemporaryDirectory
24
24
25
25
26 class MockEvent(object):
26 class MockEvent(object):
27 def __init__(self, line):
27 def __init__(self, line):
28 self.line = line
28 self.line = line
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Test functions begin
31 # Test functions begin
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 class Test_magic_run_completer(unittest.TestCase):
33 class Test_magic_run_completer(unittest.TestCase):
34 def setUp(self):
34 def setUp(self):
35 self.BASETESTDIR = tempfile.mkdtemp()
35 self.BASETESTDIR = tempfile.mkdtemp()
36 for fil in [u"aaø.py", u"a.py", u"b.py"]:
36 for fil in [u"aaø.py", u"a.py", u"b.py"]:
37 with open(join(self.BASETESTDIR, fil), "w") as sfile:
37 with open(join(self.BASETESTDIR, fil), "w") as sfile:
38 sfile.write("pass\n")
38 sfile.write("pass\n")
39 self.oldpath = os.getcwdu()
39 self.oldpath = os.getcwdu()
40 os.chdir(self.BASETESTDIR)
40 os.chdir(self.BASETESTDIR)
41
41
42 def tearDown(self):
42 def tearDown(self):
43 os.chdir(self.oldpath)
43 os.chdir(self.oldpath)
44 shutil.rmtree(self.BASETESTDIR)
44 shutil.rmtree(self.BASETESTDIR)
45
45
46 def test_1(self):
46 def test_1(self):
47 """Test magic_run_completer, should match two alterntives
47 """Test magic_run_completer, should match two alterntives
48 """
48 """
49 event = MockEvent(u"%run a")
49 event = MockEvent(u"%run a")
50 mockself = None
50 mockself = None
51 match = set(magic_run_completer(mockself, event))
51 match = set(magic_run_completer(mockself, event))
52 self.assertEqual(match, set([u"a.py", u"aaø.py"]))
52 self.assertEqual(match, set([u"a.py", u"aaø.py"]))
53
53
54 def test_2(self):
54 def test_2(self):
55 """Test magic_run_completer, should match one alterntive
55 """Test magic_run_completer, should match one alterntive
56 """
56 """
57 event = MockEvent(u"%run aa")
57 event = MockEvent(u"%run aa")
58 mockself = None
58 mockself = None
59 match = set(magic_run_completer(mockself, event))
59 match = set(magic_run_completer(mockself, event))
60 self.assertEqual(match, set([u"aaø.py"]))
60 self.assertEqual(match, set([u"aaø.py"]))
61
61
62 def test_3(self):
62 def test_3(self):
63 """Test magic_run_completer with unterminated " """
63 """Test magic_run_completer with unterminated " """
64 event = MockEvent(u'%run "a')
64 event = MockEvent(u'%run "a')
65 mockself = None
65 mockself = None
66 match = set(magic_run_completer(mockself, event))
66 match = set(magic_run_completer(mockself, event))
67 self.assertEqual(match, set([u"a.py", u"aaø.py"]))
67 self.assertEqual(match, set([u"a.py", u"aaø.py"]))
68
68
69 def test_import_invalid_module(self):
69 def test_import_invalid_module(self):
70 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
70 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
71 invalid_module_names = set(['foo-bar', 'foo:bar', '10foo'])
71 invalid_module_names = set(['foo-bar', 'foo:bar', '10foo'])
72 with TemporaryDirectory() as tmpdir:
72 with TemporaryDirectory() as tmpdir:
73 sys.path.insert( 0, tmpdir )
73 sys.path.insert( 0, tmpdir )
74 for name in invalid_module_names:
74 for name in invalid_module_names:
75 filename = os.path.join(tmpdir, name + '.py')
75 filename = os.path.join(tmpdir, name + '.py')
76 open(filename, 'w').close()
76 open(filename, 'w').close()
77
77
78 s = set( module_completion('import foo') )
78 s = set( module_completion('import foo') )
79 self.assertFalse( s.intersection(invalid_module_names) )
79 intersection = s.intersection(invalid_module_names)
80 self.assertFalse(intersection, intersection)
General Comments 0
You need to be logged in to leave comments. Login now