Chris@87
|
1 from __future__ import division, absolute_import, print_function
|
Chris@87
|
2
|
Chris@87
|
3 import os
|
Chris@87
|
4 import sys
|
Chris@87
|
5
|
Chris@87
|
6 __all__ = ['PackageLoader']
|
Chris@87
|
7
|
Chris@87
|
8 class PackageLoader(object):
|
Chris@87
|
9 def __init__(self, verbose=False, infunc=False):
|
Chris@87
|
10 """ Manages loading packages.
|
Chris@87
|
11 """
|
Chris@87
|
12
|
Chris@87
|
13 if infunc:
|
Chris@87
|
14 _level = 2
|
Chris@87
|
15 else:
|
Chris@87
|
16 _level = 1
|
Chris@87
|
17 self.parent_frame = frame = sys._getframe(_level)
|
Chris@87
|
18 self.parent_name = eval('__name__', frame.f_globals, frame.f_locals)
|
Chris@87
|
19 parent_path = eval('__path__', frame.f_globals, frame.f_locals)
|
Chris@87
|
20 if isinstance(parent_path, str):
|
Chris@87
|
21 parent_path = [parent_path]
|
Chris@87
|
22 self.parent_path = parent_path
|
Chris@87
|
23 if '__all__' not in frame.f_locals:
|
Chris@87
|
24 exec('__all__ = []', frame.f_globals, frame.f_locals)
|
Chris@87
|
25 self.parent_export_names = eval('__all__', frame.f_globals, frame.f_locals)
|
Chris@87
|
26
|
Chris@87
|
27 self.info_modules = {}
|
Chris@87
|
28 self.imported_packages = []
|
Chris@87
|
29 self.verbose = None
|
Chris@87
|
30
|
Chris@87
|
31 def _get_info_files(self, package_dir, parent_path, parent_package=None):
|
Chris@87
|
32 """ Return list of (package name,info.py file) from parent_path subdirectories.
|
Chris@87
|
33 """
|
Chris@87
|
34 from glob import glob
|
Chris@87
|
35 files = glob(os.path.join(parent_path, package_dir, 'info.py'))
|
Chris@87
|
36 for info_file in glob(os.path.join(parent_path, package_dir, 'info.pyc')):
|
Chris@87
|
37 if info_file[:-1] not in files:
|
Chris@87
|
38 files.append(info_file)
|
Chris@87
|
39 info_files = []
|
Chris@87
|
40 for info_file in files:
|
Chris@87
|
41 package_name = os.path.dirname(info_file[len(parent_path)+1:])\
|
Chris@87
|
42 .replace(os.sep, '.')
|
Chris@87
|
43 if parent_package:
|
Chris@87
|
44 package_name = parent_package + '.' + package_name
|
Chris@87
|
45 info_files.append((package_name, info_file))
|
Chris@87
|
46 info_files.extend(self._get_info_files('*',
|
Chris@87
|
47 os.path.dirname(info_file),
|
Chris@87
|
48 package_name))
|
Chris@87
|
49 return info_files
|
Chris@87
|
50
|
Chris@87
|
51 def _init_info_modules(self, packages=None):
|
Chris@87
|
52 """Initialize info_modules = {<package_name>: <package info.py module>}.
|
Chris@87
|
53 """
|
Chris@87
|
54 import imp
|
Chris@87
|
55 info_files = []
|
Chris@87
|
56 info_modules = self.info_modules
|
Chris@87
|
57
|
Chris@87
|
58 if packages is None:
|
Chris@87
|
59 for path in self.parent_path:
|
Chris@87
|
60 info_files.extend(self._get_info_files('*', path))
|
Chris@87
|
61 else:
|
Chris@87
|
62 for package_name in packages:
|
Chris@87
|
63 package_dir = os.path.join(*package_name.split('.'))
|
Chris@87
|
64 for path in self.parent_path:
|
Chris@87
|
65 names_files = self._get_info_files(package_dir, path)
|
Chris@87
|
66 if names_files:
|
Chris@87
|
67 info_files.extend(names_files)
|
Chris@87
|
68 break
|
Chris@87
|
69 else:
|
Chris@87
|
70 try:
|
Chris@87
|
71 exec('import %s.info as info' % (package_name))
|
Chris@87
|
72 info_modules[package_name] = info
|
Chris@87
|
73 except ImportError as msg:
|
Chris@87
|
74 self.warn('No scipy-style subpackage %r found in %s. '\
|
Chris@87
|
75 'Ignoring: %s'\
|
Chris@87
|
76 % (package_name, ':'.join(self.parent_path), msg))
|
Chris@87
|
77
|
Chris@87
|
78 for package_name, info_file in info_files:
|
Chris@87
|
79 if package_name in info_modules:
|
Chris@87
|
80 continue
|
Chris@87
|
81 fullname = self.parent_name +'.'+ package_name
|
Chris@87
|
82 if info_file[-1]=='c':
|
Chris@87
|
83 filedescriptor = ('.pyc', 'rb', 2)
|
Chris@87
|
84 else:
|
Chris@87
|
85 filedescriptor = ('.py', 'U', 1)
|
Chris@87
|
86
|
Chris@87
|
87 try:
|
Chris@87
|
88 info_module = imp.load_module(fullname+'.info',
|
Chris@87
|
89 open(info_file, filedescriptor[1]),
|
Chris@87
|
90 info_file,
|
Chris@87
|
91 filedescriptor)
|
Chris@87
|
92 except Exception as msg:
|
Chris@87
|
93 self.error(msg)
|
Chris@87
|
94 info_module = None
|
Chris@87
|
95
|
Chris@87
|
96 if info_module is None or getattr(info_module, 'ignore', False):
|
Chris@87
|
97 info_modules.pop(package_name, None)
|
Chris@87
|
98 else:
|
Chris@87
|
99 self._init_info_modules(getattr(info_module, 'depends', []))
|
Chris@87
|
100 info_modules[package_name] = info_module
|
Chris@87
|
101
|
Chris@87
|
102 return
|
Chris@87
|
103
|
Chris@87
|
104 def _get_sorted_names(self):
|
Chris@87
|
105 """ Return package names sorted in the order as they should be
|
Chris@87
|
106 imported due to dependence relations between packages.
|
Chris@87
|
107 """
|
Chris@87
|
108
|
Chris@87
|
109 depend_dict = {}
|
Chris@87
|
110 for name, info_module in self.info_modules.items():
|
Chris@87
|
111 depend_dict[name] = getattr(info_module, 'depends', [])
|
Chris@87
|
112 package_names = []
|
Chris@87
|
113
|
Chris@87
|
114 for name in list(depend_dict.keys()):
|
Chris@87
|
115 if not depend_dict[name]:
|
Chris@87
|
116 package_names.append(name)
|
Chris@87
|
117 del depend_dict[name]
|
Chris@87
|
118
|
Chris@87
|
119 while depend_dict:
|
Chris@87
|
120 for name, lst in list(depend_dict.items()):
|
Chris@87
|
121 new_lst = [n for n in lst if n in depend_dict]
|
Chris@87
|
122 if not new_lst:
|
Chris@87
|
123 package_names.append(name)
|
Chris@87
|
124 del depend_dict[name]
|
Chris@87
|
125 else:
|
Chris@87
|
126 depend_dict[name] = new_lst
|
Chris@87
|
127
|
Chris@87
|
128 return package_names
|
Chris@87
|
129
|
Chris@87
|
130 def __call__(self,*packages, **options):
|
Chris@87
|
131 """Load one or more packages into parent package top-level namespace.
|
Chris@87
|
132
|
Chris@87
|
133 This function is intended to shorten the need to import many
|
Chris@87
|
134 subpackages, say of scipy, constantly with statements such as
|
Chris@87
|
135
|
Chris@87
|
136 import scipy.linalg, scipy.fftpack, scipy.etc...
|
Chris@87
|
137
|
Chris@87
|
138 Instead, you can say:
|
Chris@87
|
139
|
Chris@87
|
140 import scipy
|
Chris@87
|
141 scipy.pkgload('linalg','fftpack',...)
|
Chris@87
|
142
|
Chris@87
|
143 or
|
Chris@87
|
144
|
Chris@87
|
145 scipy.pkgload()
|
Chris@87
|
146
|
Chris@87
|
147 to load all of them in one call.
|
Chris@87
|
148
|
Chris@87
|
149 If a name which doesn't exist in scipy's namespace is
|
Chris@87
|
150 given, a warning is shown.
|
Chris@87
|
151
|
Chris@87
|
152 Parameters
|
Chris@87
|
153 ----------
|
Chris@87
|
154 *packages : arg-tuple
|
Chris@87
|
155 the names (one or more strings) of all the modules one
|
Chris@87
|
156 wishes to load into the top-level namespace.
|
Chris@87
|
157 verbose= : integer
|
Chris@87
|
158 verbosity level [default: -1].
|
Chris@87
|
159 verbose=-1 will suspend also warnings.
|
Chris@87
|
160 force= : bool
|
Chris@87
|
161 when True, force reloading loaded packages [default: False].
|
Chris@87
|
162 postpone= : bool
|
Chris@87
|
163 when True, don't load packages [default: False]
|
Chris@87
|
164
|
Chris@87
|
165 """
|
Chris@87
|
166 frame = self.parent_frame
|
Chris@87
|
167 self.info_modules = {}
|
Chris@87
|
168 if options.get('force', False):
|
Chris@87
|
169 self.imported_packages = []
|
Chris@87
|
170 self.verbose = verbose = options.get('verbose', -1)
|
Chris@87
|
171 postpone = options.get('postpone', None)
|
Chris@87
|
172 self._init_info_modules(packages or None)
|
Chris@87
|
173
|
Chris@87
|
174 self.log('Imports to %r namespace\n----------------------------'\
|
Chris@87
|
175 % self.parent_name)
|
Chris@87
|
176
|
Chris@87
|
177 for package_name in self._get_sorted_names():
|
Chris@87
|
178 if package_name in self.imported_packages:
|
Chris@87
|
179 continue
|
Chris@87
|
180 info_module = self.info_modules[package_name]
|
Chris@87
|
181 global_symbols = getattr(info_module, 'global_symbols', [])
|
Chris@87
|
182 postpone_import = getattr(info_module, 'postpone_import', False)
|
Chris@87
|
183 if (postpone and not global_symbols) \
|
Chris@87
|
184 or (postpone_import and postpone is not None):
|
Chris@87
|
185 continue
|
Chris@87
|
186
|
Chris@87
|
187 old_object = frame.f_locals.get(package_name, None)
|
Chris@87
|
188
|
Chris@87
|
189 cmdstr = 'import '+package_name
|
Chris@87
|
190 if self._execcmd(cmdstr):
|
Chris@87
|
191 continue
|
Chris@87
|
192 self.imported_packages.append(package_name)
|
Chris@87
|
193
|
Chris@87
|
194 if verbose!=-1:
|
Chris@87
|
195 new_object = frame.f_locals.get(package_name)
|
Chris@87
|
196 if old_object is not None and old_object is not new_object:
|
Chris@87
|
197 self.warn('Overwriting %s=%s (was %s)' \
|
Chris@87
|
198 % (package_name, self._obj2repr(new_object),
|
Chris@87
|
199 self._obj2repr(old_object)))
|
Chris@87
|
200
|
Chris@87
|
201 if '.' not in package_name:
|
Chris@87
|
202 self.parent_export_names.append(package_name)
|
Chris@87
|
203
|
Chris@87
|
204 for symbol in global_symbols:
|
Chris@87
|
205 if symbol=='*':
|
Chris@87
|
206 symbols = eval('getattr(%s,"__all__",None)'\
|
Chris@87
|
207 % (package_name),
|
Chris@87
|
208 frame.f_globals, frame.f_locals)
|
Chris@87
|
209 if symbols is None:
|
Chris@87
|
210 symbols = eval('dir(%s)' % (package_name),
|
Chris@87
|
211 frame.f_globals, frame.f_locals)
|
Chris@87
|
212 symbols = [s for s in symbols if not s.startswith('_')]
|
Chris@87
|
213 else:
|
Chris@87
|
214 symbols = [symbol]
|
Chris@87
|
215
|
Chris@87
|
216 if verbose!=-1:
|
Chris@87
|
217 old_objects = {}
|
Chris@87
|
218 for s in symbols:
|
Chris@87
|
219 if s in frame.f_locals:
|
Chris@87
|
220 old_objects[s] = frame.f_locals[s]
|
Chris@87
|
221
|
Chris@87
|
222 cmdstr = 'from '+package_name+' import '+symbol
|
Chris@87
|
223 if self._execcmd(cmdstr):
|
Chris@87
|
224 continue
|
Chris@87
|
225
|
Chris@87
|
226 if verbose!=-1:
|
Chris@87
|
227 for s, old_object in old_objects.items():
|
Chris@87
|
228 new_object = frame.f_locals[s]
|
Chris@87
|
229 if new_object is not old_object:
|
Chris@87
|
230 self.warn('Overwriting %s=%s (was %s)' \
|
Chris@87
|
231 % (s, self._obj2repr(new_object),
|
Chris@87
|
232 self._obj2repr(old_object)))
|
Chris@87
|
233
|
Chris@87
|
234 if symbol=='*':
|
Chris@87
|
235 self.parent_export_names.extend(symbols)
|
Chris@87
|
236 else:
|
Chris@87
|
237 self.parent_export_names.append(symbol)
|
Chris@87
|
238
|
Chris@87
|
239 return
|
Chris@87
|
240
|
Chris@87
|
241 def _execcmd(self, cmdstr):
|
Chris@87
|
242 """ Execute command in parent_frame."""
|
Chris@87
|
243 frame = self.parent_frame
|
Chris@87
|
244 try:
|
Chris@87
|
245 exec (cmdstr, frame.f_globals, frame.f_locals)
|
Chris@87
|
246 except Exception as msg:
|
Chris@87
|
247 self.error('%s -> failed: %s' % (cmdstr, msg))
|
Chris@87
|
248 return True
|
Chris@87
|
249 else:
|
Chris@87
|
250 self.log('%s -> success' % (cmdstr))
|
Chris@87
|
251 return
|
Chris@87
|
252
|
Chris@87
|
253 def _obj2repr(self, obj):
|
Chris@87
|
254 """ Return repr(obj) with"""
|
Chris@87
|
255 module = getattr(obj, '__module__', None)
|
Chris@87
|
256 file = getattr(obj, '__file__', None)
|
Chris@87
|
257 if module is not None:
|
Chris@87
|
258 return repr(obj) + ' from ' + module
|
Chris@87
|
259 if file is not None:
|
Chris@87
|
260 return repr(obj) + ' from ' + file
|
Chris@87
|
261 return repr(obj)
|
Chris@87
|
262
|
Chris@87
|
263 def log(self, mess):
|
Chris@87
|
264 if self.verbose>1:
|
Chris@87
|
265 print(str(mess), file=sys.stderr)
|
Chris@87
|
266 def warn(self, mess):
|
Chris@87
|
267 if self.verbose>=0:
|
Chris@87
|
268 print(str(mess), file=sys.stderr)
|
Chris@87
|
269 def error(self, mess):
|
Chris@87
|
270 if self.verbose!=-1:
|
Chris@87
|
271 print(str(mess), file=sys.stderr)
|
Chris@87
|
272
|
Chris@87
|
273 def _get_doc_title(self, info_module):
|
Chris@87
|
274 """ Get the title from a package info.py file.
|
Chris@87
|
275 """
|
Chris@87
|
276 title = getattr(info_module, '__doc_title__', None)
|
Chris@87
|
277 if title is not None:
|
Chris@87
|
278 return title
|
Chris@87
|
279 title = getattr(info_module, '__doc__', None)
|
Chris@87
|
280 if title is not None:
|
Chris@87
|
281 title = title.lstrip().split('\n', 1)[0]
|
Chris@87
|
282 return title
|
Chris@87
|
283 return '* Not Available *'
|
Chris@87
|
284
|
Chris@87
|
285 def _format_titles(self,titles,colsep='---'):
|
Chris@87
|
286 display_window_width = 70 # How to determine the correct value in runtime??
|
Chris@87
|
287 lengths = [len(name)-name.find('.')-1 for (name, title) in titles]+[0]
|
Chris@87
|
288 max_length = max(lengths)
|
Chris@87
|
289 lines = []
|
Chris@87
|
290 for (name, title) in titles:
|
Chris@87
|
291 name = name[name.find('.')+1:]
|
Chris@87
|
292 w = max_length - len(name)
|
Chris@87
|
293 words = title.split()
|
Chris@87
|
294 line = '%s%s %s' % (name, w*' ', colsep)
|
Chris@87
|
295 tab = len(line) * ' '
|
Chris@87
|
296 while words:
|
Chris@87
|
297 word = words.pop(0)
|
Chris@87
|
298 if len(line)+len(word)>display_window_width:
|
Chris@87
|
299 lines.append(line)
|
Chris@87
|
300 line = tab
|
Chris@87
|
301 line += ' ' + word
|
Chris@87
|
302 else:
|
Chris@87
|
303 lines.append(line)
|
Chris@87
|
304 return '\n'.join(lines)
|
Chris@87
|
305
|
Chris@87
|
306 def get_pkgdocs(self):
|
Chris@87
|
307 """ Return documentation summary of subpackages.
|
Chris@87
|
308 """
|
Chris@87
|
309 import sys
|
Chris@87
|
310 self.info_modules = {}
|
Chris@87
|
311 self._init_info_modules(None)
|
Chris@87
|
312
|
Chris@87
|
313 titles = []
|
Chris@87
|
314 symbols = []
|
Chris@87
|
315 for package_name, info_module in self.info_modules.items():
|
Chris@87
|
316 global_symbols = getattr(info_module, 'global_symbols', [])
|
Chris@87
|
317 fullname = self.parent_name +'.'+ package_name
|
Chris@87
|
318 note = ''
|
Chris@87
|
319 if fullname not in sys.modules:
|
Chris@87
|
320 note = ' [*]'
|
Chris@87
|
321 titles.append((fullname, self._get_doc_title(info_module) + note))
|
Chris@87
|
322 if global_symbols:
|
Chris@87
|
323 symbols.append((package_name, ', '.join(global_symbols)))
|
Chris@87
|
324
|
Chris@87
|
325 retstr = self._format_titles(titles) +\
|
Chris@87
|
326 '\n [*] - using a package requires explicit import (see pkgload)'
|
Chris@87
|
327
|
Chris@87
|
328
|
Chris@87
|
329 if symbols:
|
Chris@87
|
330 retstr += """\n\nGlobal symbols from subpackages"""\
|
Chris@87
|
331 """\n-------------------------------\n""" +\
|
Chris@87
|
332 self._format_titles(symbols, '-->')
|
Chris@87
|
333
|
Chris@87
|
334 return retstr
|
Chris@87
|
335
|
Chris@87
|
336 class PackageLoaderDebug(PackageLoader):
|
Chris@87
|
337 def _execcmd(self, cmdstr):
|
Chris@87
|
338 """ Execute command in parent_frame."""
|
Chris@87
|
339 frame = self.parent_frame
|
Chris@87
|
340 print('Executing', repr(cmdstr), '...', end=' ')
|
Chris@87
|
341 sys.stdout.flush()
|
Chris@87
|
342 exec (cmdstr, frame.f_globals, frame.f_locals)
|
Chris@87
|
343 print('ok')
|
Chris@87
|
344 sys.stdout.flush()
|
Chris@87
|
345 return
|
Chris@87
|
346
|
Chris@87
|
347 if int(os.environ.get('NUMPY_IMPORT_DEBUG', '0')):
|
Chris@87
|
348 PackageLoader = PackageLoaderDebug
|