1
# -*- coding: utf-8 -*-
2
"""Module completion auxiliary functions"""
4
#------------------------------------------------------------------------------
6
# Most functions on this file were taken from the file core/completerlib,
7
# which belongs to the IPython project (v0.13). They were added here because
8
# a) IPython is not an Spyder runtime dependency, and b) we want to perfom
9
# module completion not only on our Python console, but also on our source
12
# Several of these functions were modified to make it work according to our
15
# Distributed under the terms of the BSD License.
16
# Copyright (C) 2010-2011 The IPython Development Team.
17
# Copyright (C) 2013 The Spyder Development Team
19
#------------------------------------------------------------------------------
28
from zipimport import zipimporter
30
from spyderlib.baseconfig import get_conf_path
31
from spyderlib.utils.external.pickleshare import PickleShareDB
33
#-----------------------------------------------------------------------------
34
# Globals and constants
35
#-----------------------------------------------------------------------------
37
# Path to the modules database
38
MODULES_PATH = get_conf_path('db')
40
# Time in seconds after which we give up
43
# Py2app only uses .pyc files for the stdlib when optimize=0,
44
# so we need to add it as another suffix here
45
if sys.platform == 'darwin' and 'Spyder.app' in __file__:
46
suffixes = imp.get_suffixes() + [('.pyc', 'rb', '2')]
48
suffixes = imp.get_suffixes()
50
# Regular expression for the python import statement
51
import_re = re.compile(r'(?P<name>[a-zA-Z_][a-zA-Z0-9_]*?)'
52
r'(?P<package>[/\\]__init__)?'
54
r'|'.join(re.escape(s[0]) for s in suffixes))
57
modules_db = PickleShareDB(MODULES_PATH)
59
#-----------------------------------------------------------------------------
61
#-----------------------------------------------------------------------------
63
def module_list(path):
65
Return the list containing the names of the modules available in the given
68
# sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
72
# A few local constants to be used in loops below
75
if os.path.isdir(path):
76
# Build a list of all files in the directory and all files
77
# in its subdirectories. For performance reasons, do not
78
# recurse more than one level into subdirectories.
80
for root, dirs, nondirs in os.walk(path):
81
subdir = root[len(path)+1:]
83
files.extend(pjoin(subdir, f) for f in nondirs)
84
dirs[:] = [] # Do not recurse into additional subdirectories.
89
files = list(zipimporter(path)._files.keys())
93
# Build a list of modules which match the import_re regex.
96
m = import_re.match(f)
98
modules.append(m.group('name'))
99
return list(set(modules))
102
def get_root_modules(paths):
104
Returns list of names of all modules from PYTHONPATH folders.
107
A list of additional paths that Spyder adds to PYTHONPATH. They are
108
comming from our PYTHONPATH manager and from the currently selected
115
spy_modules += module_list(path)
116
spy_modules = set(spy_modules)
117
if '__init__' in spy_modules:
118
spy_modules.remove('__init__')
119
spy_modules = list(spy_modules)
121
if modules_db.has_key('rootmodules'):
122
return spy_modules + modules_db['rootmodules']
125
modules = list(sys.builtin_module_names)
126
# TODO: Change this sys.path for console's interpreter sys.path
127
for path in sys.path:
128
modules += module_list(path)
129
if time() - t > TIMEOUT_GIVEUP:
130
print "Module list generation is taking too long, we give up.\n"
131
modules_db['rootmodules'] = []
134
modules = set(modules)
135
excluded_modules = ['__init__'] + spy_modules
136
for mod in excluded_modules:
139
modules = list(modules)
141
modules_db['rootmodules'] = modules
142
return spy_modules + modules
145
def get_submodules(mod):
146
"""Get all submodules of a given module"""
147
def catch_exceptions(module):
152
submods = pkgutil.walk_packages(m.__path__, m.__name__ + '.',
156
submodules.append(sm_name)
165
def is_importable(module, attr, only_modules):
167
return inspect.ismodule(getattr(module, attr))
169
return not(attr[:2] == '__' and attr[-2:] == '__')
172
def try_import(mod, only_modules=False):
177
mods = mod.split('.')
178
for module in mods[1:]:
179
m = getattr(m, module)
181
m_is_init = hasattr(m, '__file__') and '__init__' in m.__file__
184
if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
185
completions.extend([attr for attr in dir(m) if
186
is_importable(m, attr, only_modules)])
188
completions.extend(getattr(m, '__all__', []))
190
completions.extend(module_list(os.path.dirname(m.__file__)))
191
completions = set(completions)
192
if '__init__' in completions:
193
completions.remove('__init__')
194
return list(completions)
197
def dot_completion(mod, paths):
199
return filter(lambda x: x.startswith(mod[0]), get_root_modules(paths))
200
completion_list = try_import('.'.join(mod[:-1]), True)
201
completion_list = filter(lambda x: x.startswith(mod[-1]), completion_list)
202
completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
203
return completion_list
205
#-----------------------------------------------------------------------------
207
#-----------------------------------------------------------------------------
209
def module_completion(line, paths=[]):
211
Returns a list containing the completion possibilities for an import line.
213
The line looks like this :
215
'from xml.dom import'
218
words = line.split(' ')
221
# from whatever <tab> -> 'import '
222
if nwords == 3 and words[0] == 'from':
223
if words[2].startswith('i') or words[2] == '':
228
# 'import xy<tab> or import xy<tab>, '
229
if words[0] == 'import':
230
if nwords == 2 and words[1] == '':
231
return get_root_modules(paths)
232
if ',' == words[-1][-1]:
234
mod = words[-1].split('.')
235
return dot_completion(mod, paths)
238
if nwords < 3 and (words[0] == 'from'):
240
return get_root_modules(paths)
241
mod = words[1].split('.')
242
return dot_completion(mod, paths)
244
# 'from xyz import abc<tab>'
245
if nwords >= 3 and words[0] == 'from':
247
completion_list = try_import(mod)
248
if words[2] == 'import' and words[3] != '':
250
words = words[:-2] + words[-1].split('(')
252
words = words[:-2] + words[-1].split(',')
253
return filter(lambda x: x.startswith(words[-1]), completion_list)
255
return completion_list
261
"""Clear root modules database"""
262
if modules_db.has_key('rootmodules'):
263
del modules_db['rootmodules']
266
def get_preferred_submodules():
268
Get all submodules of the main scientific modules and others of our
271
if modules_db.has_key('submodules'):
272
return modules_db['submodules']
274
mods = ['numpy', 'scipy', 'sympy', 'pandas', 'networkx', 'statsmodels',
275
'matplotlib', 'sklearn', 'skimage', 'mpmath', 'os', 'PIL',
276
'OpenGL', 'array', 'audioop', 'binascii', 'cPickle', 'cStringIO',
277
'cmath', 'collections', 'datetime', 'errno', 'exceptions', 'gc',
278
'imageop', 'imp', 'itertools', 'marshal', 'math', 'mmap', 'msvcrt',
279
'nt', 'operator', 'parser', 'rgbimg', 'signal', 'strop', 'sys',
280
'thread', 'time', 'wx', 'xxsubtype', 'zipimport', 'zlib', 'nose',
281
'PyQt4', 'PySide', 'os.path']
285
submods = get_submodules(m)
286
submodules += submods
288
modules_db['submodules'] = submodules
291
#-----------------------------------------------------------------------------
293
#-----------------------------------------------------------------------------
295
if __name__ == "__main__":
297
# Sort operations are done by the completion widget, so we have to
298
# replicate them here.
299
# We've chosen to use xml on most tests because it's on the standard
300
# library. This way we can ensure they work on all plataforms.
302
assert sorted(module_completion('import xml.')) == \
303
['xml.dom', 'xml.etree', 'xml.parsers', 'xml.sax']
305
assert sorted(module_completion('import xml.d')) == ['xml.dom']
307
assert module_completion('from xml.etree ') == ['import ']
309
assert sorted(module_completion('from xml.etree import '), key=str.lower) ==\
310
['cElementTree', 'ElementInclude', 'ElementPath', 'ElementTree']
312
assert module_completion('import sys, zl') == ['zlib']
314
s = 'from xml.etree.ElementTree import '
315
assert module_completion(s + 'V') == ['VERSION']
317
assert sorted(module_completion(s + 'VERSION, XM')) == \
318
['XML', 'XMLID', 'XMLParser', 'XMLTreeBuilder']
320
assert module_completion(s + '(dum') == ['dump']
322
assert module_completion(s + '(dump, Su') == ['SubElement']