Chris@87: from __future__ import division, absolute_import, print_function Chris@87: Chris@87: import os Chris@87: import sys Chris@87: Chris@87: __all__ = ['PackageLoader'] Chris@87: Chris@87: class PackageLoader(object): Chris@87: def __init__(self, verbose=False, infunc=False): Chris@87: """ Manages loading packages. Chris@87: """ Chris@87: Chris@87: if infunc: Chris@87: _level = 2 Chris@87: else: Chris@87: _level = 1 Chris@87: self.parent_frame = frame = sys._getframe(_level) Chris@87: self.parent_name = eval('__name__', frame.f_globals, frame.f_locals) Chris@87: parent_path = eval('__path__', frame.f_globals, frame.f_locals) Chris@87: if isinstance(parent_path, str): Chris@87: parent_path = [parent_path] Chris@87: self.parent_path = parent_path Chris@87: if '__all__' not in frame.f_locals: Chris@87: exec('__all__ = []', frame.f_globals, frame.f_locals) Chris@87: self.parent_export_names = eval('__all__', frame.f_globals, frame.f_locals) Chris@87: Chris@87: self.info_modules = {} Chris@87: self.imported_packages = [] Chris@87: self.verbose = None Chris@87: Chris@87: def _get_info_files(self, package_dir, parent_path, parent_package=None): Chris@87: """ Return list of (package name,info.py file) from parent_path subdirectories. Chris@87: """ Chris@87: from glob import glob Chris@87: files = glob(os.path.join(parent_path, package_dir, 'info.py')) Chris@87: for info_file in glob(os.path.join(parent_path, package_dir, 'info.pyc')): Chris@87: if info_file[:-1] not in files: Chris@87: files.append(info_file) Chris@87: info_files = [] Chris@87: for info_file in files: Chris@87: package_name = os.path.dirname(info_file[len(parent_path)+1:])\ Chris@87: .replace(os.sep, '.') Chris@87: if parent_package: Chris@87: package_name = parent_package + '.' + package_name Chris@87: info_files.append((package_name, info_file)) Chris@87: info_files.extend(self._get_info_files('*', Chris@87: os.path.dirname(info_file), Chris@87: package_name)) Chris@87: return info_files Chris@87: Chris@87: def _init_info_modules(self, packages=None): Chris@87: """Initialize info_modules = {: }. Chris@87: """ Chris@87: import imp Chris@87: info_files = [] Chris@87: info_modules = self.info_modules Chris@87: Chris@87: if packages is None: Chris@87: for path in self.parent_path: Chris@87: info_files.extend(self._get_info_files('*', path)) Chris@87: else: Chris@87: for package_name in packages: Chris@87: package_dir = os.path.join(*package_name.split('.')) Chris@87: for path in self.parent_path: Chris@87: names_files = self._get_info_files(package_dir, path) Chris@87: if names_files: Chris@87: info_files.extend(names_files) Chris@87: break Chris@87: else: Chris@87: try: Chris@87: exec('import %s.info as info' % (package_name)) Chris@87: info_modules[package_name] = info Chris@87: except ImportError as msg: Chris@87: self.warn('No scipy-style subpackage %r found in %s. '\ Chris@87: 'Ignoring: %s'\ Chris@87: % (package_name, ':'.join(self.parent_path), msg)) Chris@87: Chris@87: for package_name, info_file in info_files: Chris@87: if package_name in info_modules: Chris@87: continue Chris@87: fullname = self.parent_name +'.'+ package_name Chris@87: if info_file[-1]=='c': Chris@87: filedescriptor = ('.pyc', 'rb', 2) Chris@87: else: Chris@87: filedescriptor = ('.py', 'U', 1) Chris@87: Chris@87: try: Chris@87: info_module = imp.load_module(fullname+'.info', Chris@87: open(info_file, filedescriptor[1]), Chris@87: info_file, Chris@87: filedescriptor) Chris@87: except Exception as msg: Chris@87: self.error(msg) Chris@87: info_module = None Chris@87: Chris@87: if info_module is None or getattr(info_module, 'ignore', False): Chris@87: info_modules.pop(package_name, None) Chris@87: else: Chris@87: self._init_info_modules(getattr(info_module, 'depends', [])) Chris@87: info_modules[package_name] = info_module Chris@87: Chris@87: return Chris@87: Chris@87: def _get_sorted_names(self): Chris@87: """ Return package names sorted in the order as they should be Chris@87: imported due to dependence relations between packages. Chris@87: """ Chris@87: Chris@87: depend_dict = {} Chris@87: for name, info_module in self.info_modules.items(): Chris@87: depend_dict[name] = getattr(info_module, 'depends', []) Chris@87: package_names = [] Chris@87: Chris@87: for name in list(depend_dict.keys()): Chris@87: if not depend_dict[name]: Chris@87: package_names.append(name) Chris@87: del depend_dict[name] Chris@87: Chris@87: while depend_dict: Chris@87: for name, lst in list(depend_dict.items()): Chris@87: new_lst = [n for n in lst if n in depend_dict] Chris@87: if not new_lst: Chris@87: package_names.append(name) Chris@87: del depend_dict[name] Chris@87: else: Chris@87: depend_dict[name] = new_lst Chris@87: Chris@87: return package_names Chris@87: Chris@87: def __call__(self,*packages, **options): Chris@87: """Load one or more packages into parent package top-level namespace. Chris@87: Chris@87: This function is intended to shorten the need to import many Chris@87: subpackages, say of scipy, constantly with statements such as Chris@87: Chris@87: import scipy.linalg, scipy.fftpack, scipy.etc... Chris@87: Chris@87: Instead, you can say: Chris@87: Chris@87: import scipy Chris@87: scipy.pkgload('linalg','fftpack',...) Chris@87: Chris@87: or Chris@87: Chris@87: scipy.pkgload() Chris@87: Chris@87: to load all of them in one call. Chris@87: Chris@87: If a name which doesn't exist in scipy's namespace is Chris@87: given, a warning is shown. Chris@87: Chris@87: Parameters Chris@87: ---------- Chris@87: *packages : arg-tuple Chris@87: the names (one or more strings) of all the modules one Chris@87: wishes to load into the top-level namespace. Chris@87: verbose= : integer Chris@87: verbosity level [default: -1]. Chris@87: verbose=-1 will suspend also warnings. Chris@87: force= : bool Chris@87: when True, force reloading loaded packages [default: False]. Chris@87: postpone= : bool Chris@87: when True, don't load packages [default: False] Chris@87: Chris@87: """ Chris@87: frame = self.parent_frame Chris@87: self.info_modules = {} Chris@87: if options.get('force', False): Chris@87: self.imported_packages = [] Chris@87: self.verbose = verbose = options.get('verbose', -1) Chris@87: postpone = options.get('postpone', None) Chris@87: self._init_info_modules(packages or None) Chris@87: Chris@87: self.log('Imports to %r namespace\n----------------------------'\ Chris@87: % self.parent_name) Chris@87: Chris@87: for package_name in self._get_sorted_names(): Chris@87: if package_name in self.imported_packages: Chris@87: continue Chris@87: info_module = self.info_modules[package_name] Chris@87: global_symbols = getattr(info_module, 'global_symbols', []) Chris@87: postpone_import = getattr(info_module, 'postpone_import', False) Chris@87: if (postpone and not global_symbols) \ Chris@87: or (postpone_import and postpone is not None): Chris@87: continue Chris@87: Chris@87: old_object = frame.f_locals.get(package_name, None) Chris@87: Chris@87: cmdstr = 'import '+package_name Chris@87: if self._execcmd(cmdstr): Chris@87: continue Chris@87: self.imported_packages.append(package_name) Chris@87: Chris@87: if verbose!=-1: Chris@87: new_object = frame.f_locals.get(package_name) Chris@87: if old_object is not None and old_object is not new_object: Chris@87: self.warn('Overwriting %s=%s (was %s)' \ Chris@87: % (package_name, self._obj2repr(new_object), Chris@87: self._obj2repr(old_object))) Chris@87: Chris@87: if '.' not in package_name: Chris@87: self.parent_export_names.append(package_name) Chris@87: Chris@87: for symbol in global_symbols: Chris@87: if symbol=='*': Chris@87: symbols = eval('getattr(%s,"__all__",None)'\ Chris@87: % (package_name), Chris@87: frame.f_globals, frame.f_locals) Chris@87: if symbols is None: Chris@87: symbols = eval('dir(%s)' % (package_name), Chris@87: frame.f_globals, frame.f_locals) Chris@87: symbols = [s for s in symbols if not s.startswith('_')] Chris@87: else: Chris@87: symbols = [symbol] Chris@87: Chris@87: if verbose!=-1: Chris@87: old_objects = {} Chris@87: for s in symbols: Chris@87: if s in frame.f_locals: Chris@87: old_objects[s] = frame.f_locals[s] Chris@87: Chris@87: cmdstr = 'from '+package_name+' import '+symbol Chris@87: if self._execcmd(cmdstr): Chris@87: continue Chris@87: Chris@87: if verbose!=-1: Chris@87: for s, old_object in old_objects.items(): Chris@87: new_object = frame.f_locals[s] Chris@87: if new_object is not old_object: Chris@87: self.warn('Overwriting %s=%s (was %s)' \ Chris@87: % (s, self._obj2repr(new_object), Chris@87: self._obj2repr(old_object))) Chris@87: Chris@87: if symbol=='*': Chris@87: self.parent_export_names.extend(symbols) Chris@87: else: Chris@87: self.parent_export_names.append(symbol) Chris@87: Chris@87: return Chris@87: Chris@87: def _execcmd(self, cmdstr): Chris@87: """ Execute command in parent_frame.""" Chris@87: frame = self.parent_frame Chris@87: try: Chris@87: exec (cmdstr, frame.f_globals, frame.f_locals) Chris@87: except Exception as msg: Chris@87: self.error('%s -> failed: %s' % (cmdstr, msg)) Chris@87: return True Chris@87: else: Chris@87: self.log('%s -> success' % (cmdstr)) Chris@87: return Chris@87: Chris@87: def _obj2repr(self, obj): Chris@87: """ Return repr(obj) with""" Chris@87: module = getattr(obj, '__module__', None) Chris@87: file = getattr(obj, '__file__', None) Chris@87: if module is not None: Chris@87: return repr(obj) + ' from ' + module Chris@87: if file is not None: Chris@87: return repr(obj) + ' from ' + file Chris@87: return repr(obj) Chris@87: Chris@87: def log(self, mess): Chris@87: if self.verbose>1: Chris@87: print(str(mess), file=sys.stderr) Chris@87: def warn(self, mess): Chris@87: if self.verbose>=0: Chris@87: print(str(mess), file=sys.stderr) Chris@87: def error(self, mess): Chris@87: if self.verbose!=-1: Chris@87: print(str(mess), file=sys.stderr) Chris@87: Chris@87: def _get_doc_title(self, info_module): Chris@87: """ Get the title from a package info.py file. Chris@87: """ Chris@87: title = getattr(info_module, '__doc_title__', None) Chris@87: if title is not None: Chris@87: return title Chris@87: title = getattr(info_module, '__doc__', None) Chris@87: if title is not None: Chris@87: title = title.lstrip().split('\n', 1)[0] Chris@87: return title Chris@87: return '* Not Available *' Chris@87: Chris@87: def _format_titles(self,titles,colsep='---'): Chris@87: display_window_width = 70 # How to determine the correct value in runtime?? Chris@87: lengths = [len(name)-name.find('.')-1 for (name, title) in titles]+[0] Chris@87: max_length = max(lengths) Chris@87: lines = [] Chris@87: for (name, title) in titles: Chris@87: name = name[name.find('.')+1:] Chris@87: w = max_length - len(name) Chris@87: words = title.split() Chris@87: line = '%s%s %s' % (name, w*' ', colsep) Chris@87: tab = len(line) * ' ' Chris@87: while words: Chris@87: word = words.pop(0) Chris@87: if len(line)+len(word)>display_window_width: Chris@87: lines.append(line) Chris@87: line = tab Chris@87: line += ' ' + word Chris@87: else: Chris@87: lines.append(line) Chris@87: return '\n'.join(lines) Chris@87: Chris@87: def get_pkgdocs(self): Chris@87: """ Return documentation summary of subpackages. Chris@87: """ Chris@87: import sys Chris@87: self.info_modules = {} Chris@87: self._init_info_modules(None) Chris@87: Chris@87: titles = [] Chris@87: symbols = [] Chris@87: for package_name, info_module in self.info_modules.items(): Chris@87: global_symbols = getattr(info_module, 'global_symbols', []) Chris@87: fullname = self.parent_name +'.'+ package_name Chris@87: note = '' Chris@87: if fullname not in sys.modules: Chris@87: note = ' [*]' Chris@87: titles.append((fullname, self._get_doc_title(info_module) + note)) Chris@87: if global_symbols: Chris@87: symbols.append((package_name, ', '.join(global_symbols))) Chris@87: Chris@87: retstr = self._format_titles(titles) +\ Chris@87: '\n [*] - using a package requires explicit import (see pkgload)' Chris@87: Chris@87: Chris@87: if symbols: Chris@87: retstr += """\n\nGlobal symbols from subpackages"""\ Chris@87: """\n-------------------------------\n""" +\ Chris@87: self._format_titles(symbols, '-->') Chris@87: Chris@87: return retstr Chris@87: Chris@87: class PackageLoaderDebug(PackageLoader): Chris@87: def _execcmd(self, cmdstr): Chris@87: """ Execute command in parent_frame.""" Chris@87: frame = self.parent_frame Chris@87: print('Executing', repr(cmdstr), '...', end=' ') Chris@87: sys.stdout.flush() Chris@87: exec (cmdstr, frame.f_globals, frame.f_locals) Chris@87: print('ok') Chris@87: sys.stdout.flush() Chris@87: return Chris@87: Chris@87: if int(os.environ.get('NUMPY_IMPORT_DEBUG', '0')): Chris@87: PackageLoader = PackageLoaderDebug