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