##// END OF EJS Templates
Ensure `try_import` does not fail on null module.__file__
Wei Yen -
Show More
@@ -1,354 +1,354 b''
1 1 # encoding: utf-8
2 2 """Implementations for various useful completers.
3 3
4 4 These are all loaded by default by IPython.
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Stdlib imports
19 19 import glob
20 20 import inspect
21 21 import os
22 22 import re
23 23 import sys
24 24 from importlib import import_module
25 25 from importlib.machinery import all_suffixes
26 26
27 27
28 28 # Third-party imports
29 29 from time import time
30 30 from zipimport import zipimporter
31 31
32 32 # Our own imports
33 33 from IPython.core.completer import expand_user, compress_user
34 34 from IPython.core.error import TryNext
35 35 from IPython.utils._process_common import arg_split
36 36
37 37 # FIXME: this should be pulled in with the right call via the component system
38 38 from IPython import get_ipython
39 39
40 40 from typing import List
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # Globals and constants
44 44 #-----------------------------------------------------------------------------
45 45 _suffixes = all_suffixes()
46 46
47 47 # Time in seconds after which the rootmodules will be stored permanently in the
48 48 # ipython ip.db database (kept in the user's .ipython dir).
49 49 TIMEOUT_STORAGE = 2
50 50
51 51 # Time in seconds after which we give up
52 52 TIMEOUT_GIVEUP = 20
53 53
54 54 # Regular expression for the python import statement
55 55 import_re = re.compile(r'(?P<name>[a-zA-Z_][a-zA-Z0-9_]*?)'
56 56 r'(?P<package>[/\\]__init__)?'
57 57 r'(?P<suffix>%s)$' %
58 58 r'|'.join(re.escape(s) for s in _suffixes))
59 59
60 60 # RE for the ipython %run command (python + ipython scripts)
61 61 magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$')
62 62
63 63 #-----------------------------------------------------------------------------
64 64 # Local utilities
65 65 #-----------------------------------------------------------------------------
66 66
67 67 def module_list(path):
68 68 """
69 69 Return the list containing the names of the modules available in the given
70 70 folder.
71 71 """
72 72 # sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
73 73 if path == '':
74 74 path = '.'
75 75
76 76 # A few local constants to be used in loops below
77 77 pjoin = os.path.join
78 78
79 79 if os.path.isdir(path):
80 80 # Build a list of all files in the directory and all files
81 81 # in its subdirectories. For performance reasons, do not
82 82 # recurse more than one level into subdirectories.
83 83 files = []
84 84 for root, dirs, nondirs in os.walk(path, followlinks=True):
85 85 subdir = root[len(path)+1:]
86 86 if subdir:
87 87 files.extend(pjoin(subdir, f) for f in nondirs)
88 88 dirs[:] = [] # Do not recurse into additional subdirectories.
89 89 else:
90 90 files.extend(nondirs)
91 91
92 92 else:
93 93 try:
94 94 files = list(zipimporter(path)._files.keys())
95 95 except:
96 96 files = []
97 97
98 98 # Build a list of modules which match the import_re regex.
99 99 modules = []
100 100 for f in files:
101 101 m = import_re.match(f)
102 102 if m:
103 103 modules.append(m.group('name'))
104 104 return list(set(modules))
105 105
106 106
107 107 def get_root_modules():
108 108 """
109 109 Returns a list containing the names of all the modules available in the
110 110 folders of the pythonpath.
111 111
112 112 ip.db['rootmodules_cache'] maps sys.path entries to list of modules.
113 113 """
114 114 ip = get_ipython()
115 115 if ip is None:
116 116 # No global shell instance to store cached list of modules.
117 117 # Don't try to scan for modules every time.
118 118 return list(sys.builtin_module_names)
119 119
120 120 rootmodules_cache = ip.db.get('rootmodules_cache', {})
121 121 rootmodules = list(sys.builtin_module_names)
122 122 start_time = time()
123 123 store = False
124 124 for path in sys.path:
125 125 try:
126 126 modules = rootmodules_cache[path]
127 127 except KeyError:
128 128 modules = module_list(path)
129 129 try:
130 130 modules.remove('__init__')
131 131 except ValueError:
132 132 pass
133 133 if path not in ('', '.'): # cwd modules should not be cached
134 134 rootmodules_cache[path] = modules
135 135 if time() - start_time > TIMEOUT_STORAGE and not store:
136 136 store = True
137 137 print("\nCaching the list of root modules, please wait!")
138 138 print("(This will only be done once - type '%rehashx' to "
139 139 "reset cache!)\n")
140 140 sys.stdout.flush()
141 141 if time() - start_time > TIMEOUT_GIVEUP:
142 142 print("This is taking too long, we give up.\n")
143 143 return []
144 144 rootmodules.extend(modules)
145 145 if store:
146 146 ip.db['rootmodules_cache'] = rootmodules_cache
147 147 rootmodules = list(set(rootmodules))
148 148 return rootmodules
149 149
150 150
151 151 def is_importable(module, attr, only_modules):
152 152 if only_modules:
153 153 return inspect.ismodule(getattr(module, attr))
154 154 else:
155 155 return not(attr[:2] == '__' and attr[-2:] == '__')
156 156
157 157
158 158 def try_import(mod: str, only_modules=False) -> List[str]:
159 159 """
160 160 Try to import given module and return list of potential completions.
161 161 """
162 162 mod = mod.rstrip('.')
163 163 try:
164 164 m = import_module(mod)
165 165 except:
166 166 return []
167 167
168 m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
168 m_is_init = '__init__' in (getattr(m, '__file__', '') or '')
169 169
170 170 completions = []
171 171 if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
172 172 completions.extend( [attr for attr in dir(m) if
173 173 is_importable(m, attr, only_modules)])
174 174
175 175 completions.extend(getattr(m, '__all__', []))
176 176 if m_is_init:
177 177 completions.extend(module_list(os.path.dirname(m.__file__)))
178 178 completions_set = {c for c in completions if isinstance(c, str)}
179 179 completions_set.discard('__init__')
180 180 return list(completions_set)
181 181
182 182
183 183 #-----------------------------------------------------------------------------
184 184 # Completion-related functions.
185 185 #-----------------------------------------------------------------------------
186 186
187 187 def quick_completer(cmd, completions):
188 188 """ Easily create a trivial completer for a command.
189 189
190 190 Takes either a list of completions, or all completions in string (that will
191 191 be split on whitespace).
192 192
193 193 Example::
194 194
195 195 [d:\ipython]|1> import ipy_completers
196 196 [d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
197 197 [d:\ipython]|3> foo b<TAB>
198 198 bar baz
199 199 [d:\ipython]|3> foo ba
200 200 """
201 201
202 202 if isinstance(completions, str):
203 203 completions = completions.split()
204 204
205 205 def do_complete(self, event):
206 206 return completions
207 207
208 208 get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
209 209
210 210 def module_completion(line):
211 211 """
212 212 Returns a list containing the completion possibilities for an import line.
213 213
214 214 The line looks like this :
215 215 'import xml.d'
216 216 'from xml.dom import'
217 217 """
218 218
219 219 words = line.split(' ')
220 220 nwords = len(words)
221 221
222 222 # from whatever <tab> -> 'import '
223 223 if nwords == 3 and words[0] == 'from':
224 224 return ['import ']
225 225
226 226 # 'from xy<tab>' or 'import xy<tab>'
227 227 if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) :
228 228 if nwords == 1:
229 229 return get_root_modules()
230 230 mod = words[1].split('.')
231 231 if len(mod) < 2:
232 232 return get_root_modules()
233 233 completion_list = try_import('.'.join(mod[:-1]), True)
234 234 return ['.'.join(mod[:-1] + [el]) for el in completion_list]
235 235
236 236 # 'from xyz import abc<tab>'
237 237 if nwords >= 3 and words[0] == 'from':
238 238 mod = words[1]
239 239 return try_import(mod)
240 240
241 241 #-----------------------------------------------------------------------------
242 242 # Completers
243 243 #-----------------------------------------------------------------------------
244 244 # These all have the func(self, event) signature to be used as custom
245 245 # completers
246 246
247 247 def module_completer(self,event):
248 248 """Give completions after user has typed 'import ...' or 'from ...'"""
249 249
250 250 # This works in all versions of python. While 2.5 has
251 251 # pkgutil.walk_packages(), that particular routine is fairly dangerous,
252 252 # since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
253 253 # of possibly problematic side effects.
254 254 # This search the folders in the sys.path for available modules.
255 255
256 256 return module_completion(event.line)
257 257
258 258 # FIXME: there's a lot of logic common to the run, cd and builtin file
259 259 # completers, that is currently reimplemented in each.
260 260
261 261 def magic_run_completer(self, event):
262 262 """Complete files that end in .py or .ipy or .ipynb for the %run command.
263 263 """
264 264 comps = arg_split(event.line, strict=False)
265 265 # relpath should be the current token that we need to complete.
266 266 if (len(comps) > 1) and (not event.line.endswith(' ')):
267 267 relpath = comps[-1].strip("'\"")
268 268 else:
269 269 relpath = ''
270 270
271 271 #print("\nev=", event) # dbg
272 272 #print("rp=", relpath) # dbg
273 273 #print('comps=', comps) # dbg
274 274
275 275 lglob = glob.glob
276 276 isdir = os.path.isdir
277 277 relpath, tilde_expand, tilde_val = expand_user(relpath)
278 278
279 279 # Find if the user has already typed the first filename, after which we
280 280 # should complete on all files, since after the first one other files may
281 281 # be arguments to the input script.
282 282
283 283 if any(magic_run_re.match(c) for c in comps):
284 284 matches = [f.replace('\\','/') + ('/' if isdir(f) else '')
285 285 for f in lglob(relpath+'*')]
286 286 else:
287 287 dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
288 288 pys = [f.replace('\\','/')
289 289 for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
290 290 lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')]
291 291
292 292 matches = dirs + pys
293 293
294 294 #print('run comp:', dirs+pys) # dbg
295 295 return [compress_user(p, tilde_expand, tilde_val) for p in matches]
296 296
297 297
298 298 def cd_completer(self, event):
299 299 """Completer function for cd, which only returns directories."""
300 300 ip = get_ipython()
301 301 relpath = event.symbol
302 302
303 303 #print(event) # dbg
304 304 if event.line.endswith('-b') or ' -b ' in event.line:
305 305 # return only bookmark completions
306 306 bkms = self.db.get('bookmarks', None)
307 307 if bkms:
308 308 return bkms.keys()
309 309 else:
310 310 return []
311 311
312 312 if event.symbol == '-':
313 313 width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
314 314 # jump in directory history by number
315 315 fmt = '-%0' + width_dh +'d [%s]'
316 316 ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
317 317 if len(ents) > 1:
318 318 return ents
319 319 return []
320 320
321 321 if event.symbol.startswith('--'):
322 322 return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
323 323
324 324 # Expand ~ in path and normalize directory separators.
325 325 relpath, tilde_expand, tilde_val = expand_user(relpath)
326 326 relpath = relpath.replace('\\','/')
327 327
328 328 found = []
329 329 for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
330 330 if os.path.isdir(f)]:
331 331 if ' ' in d:
332 332 # we don't want to deal with any of that, complex code
333 333 # for this is elsewhere
334 334 raise TryNext
335 335
336 336 found.append(d)
337 337
338 338 if not found:
339 339 if os.path.isdir(relpath):
340 340 return [compress_user(relpath, tilde_expand, tilde_val)]
341 341
342 342 # if no completions so far, try bookmarks
343 343 bks = self.db.get('bookmarks',{})
344 344 bkmatches = [s for s in bks if s.startswith(event.symbol)]
345 345 if bkmatches:
346 346 return bkmatches
347 347
348 348 raise TryNext
349 349
350 350 return [compress_user(p, tilde_expand, tilde_val) for p in found]
351 351
352 352 def reset_completer(self, event):
353 353 "A completer for %reset magic"
354 354 return '-f -s in out array dhist'.split()
General Comments 0
You need to be logged in to leave comments. Login now