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)
62 else None
63 )
64
65 def install(self):
66 """
67 Install this importer into sys.meta_path if not already present.
68 """
69 if self not in sys.meta_path:
70 sys.meta_path.append(self)
71
72
73names = (
74 'packaging',
75 'platformdirs',
76 'jaraco',
77 'importlib_resources',
78 'more_itertools',
79 'backports',
80)
81VendorImporter(__name__, names).install()