1import importlib.util 2import sys 3 4 5class VendorImporter: 6 """ 7 A PEP 302 meta path importer for finding optionally-vendored 8 or otherwise naturally-installed packages from root_name. 9 """ 10 11 def __init__(self, root_name, vendored_names=(), vendor_pkg=None): 12 self.root_name = root_name 13 self.vendored_names = set(vendored_names) 14 self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') 15 16 @property 17 def search_path(self): 18 """ 19 Search first the vendor package then as a natural package. 20 """ 21 yield self.vendor_pkg + '.' 22 yield '' 23 24 def _module_matches_namespace(self, fullname): 25 """Figure out if the target module is vendored.""" 26 root, base, target = fullname.partition(self.root_name + '.') 27 return not root and any(map(target.startswith, self.vendored_names)) 28 29 def load_module(self, fullname): 30 """ 31 Iterate over the search path to locate and load fullname. 32 """ 33 root, base, target = fullname.partition(self.root_name + '.') 34 for prefix in self.search_path: 35 try: 36 extant = prefix + target 37 __import__(extant) 38 mod = sys.modules[extant] 39 sys.modules[fullname] = mod 40 return mod 41 except ImportError: 42 pass 43 else: 44 raise ImportError( 45 "The '{target}' package is required; " 46 "normally this is bundled with this package so if you get " 47 "this warning, consult the packager of your " 48 "distribution.".format(**locals()) 49 ) 50 51 def create_module(self, spec): 52 return self.load_module(spec.name) 53 54 def exec_module(self, module): 55 pass 56 57 def find_spec(self, fullname, path=None, target=None): 58 """Return a module spec for vendored names.""" 59 return ( 60 importlib.util.spec_from_loader(fullname, self) 61 if self._module_matches_namespace(fullname) else None 62 ) 63 64 def install(self): 65 """ 66 Install this importer into sys.meta_path if not already present. 67 """ 68 if self not in sys.meta_path: 69 sys.meta_path.append(self) 70 71 72names = ( 73 'packaging', 'pyparsing', 'appdirs', 'jaraco', 'importlib_resources', 74 'more_itertools', 75) 76VendorImporter(__name__, names).install() 77