|
| 1 | +"""Utility code for constructing importers, etc.""" |
| 2 | +from _frozen_importlib import _resolve_name |
| 3 | +from _frozen_importlib import _find_spec |
| 4 | + |
| 5 | +import sys |
| 6 | + |
| 7 | + |
| 8 | +def resolve_name(name, package): |
| 9 | + """Resolve a relative module name to an absolute one.""" |
| 10 | + if not name.startswith('.'): |
| 11 | + return name |
| 12 | + elif not package: |
| 13 | + raise ValueError(f'no package specified for {repr(name)} ' |
| 14 | + '(required for relative module names)') |
| 15 | + level = 0 |
| 16 | + for character in name: |
| 17 | + if character != '.': |
| 18 | + break |
| 19 | + level += 1 |
| 20 | + return _resolve_name(name[level:], package, level) |
| 21 | + |
| 22 | + |
| 23 | +def _find_spec_from_path(name, path=None): |
| 24 | + """Return the spec for the specified module. |
| 25 | +
|
| 26 | + First, sys.modules is checked to see if the module was already imported. If |
| 27 | + so, then sys.modules[name].__spec__ is returned. If that happens to be |
| 28 | + set to None, then ValueError is raised. If the module is not in |
| 29 | + sys.modules, then sys.meta_path is searched for a suitable spec with the |
| 30 | + value of 'path' given to the finders. None is returned if no spec could |
| 31 | + be found. |
| 32 | +
|
| 33 | + Dotted names do not have their parent packages implicitly imported. You will |
| 34 | + most likely need to explicitly import all parent packages in the proper |
| 35 | + order for a submodule to get the correct spec. |
| 36 | +
|
| 37 | + """ |
| 38 | + if name not in sys.modules: |
| 39 | + return _find_spec(name, path) |
| 40 | + else: |
| 41 | + module = sys.modules[name] |
| 42 | + if module is None: |
| 43 | + return None |
| 44 | + try: |
| 45 | + spec = module.__spec__ |
| 46 | + except AttributeError: |
| 47 | + raise ValueError('{}.__spec__ is not set'.format(name)) from None |
| 48 | + else: |
| 49 | + if spec is None: |
| 50 | + raise ValueError('{}.__spec__ is None'.format(name)) |
| 51 | + return spec |
| 52 | + |
| 53 | + |
| 54 | +def find_spec(name, package=None): |
| 55 | + """Return the spec for the specified module. |
| 56 | +
|
| 57 | + First, sys.modules is checked to see if the module was already imported. If |
| 58 | + so, then sys.modules[name].__spec__ is returned. If that happens to be |
| 59 | + set to None, then ValueError is raised. If the module is not in |
| 60 | + sys.modules, then sys.meta_path is searched for a suitable spec with the |
| 61 | + value of 'path' given to the finders. None is returned if no spec could |
| 62 | + be found. |
| 63 | +
|
| 64 | + If the name is for submodule (contains a dot), the parent module is |
| 65 | + automatically imported. |
| 66 | +
|
| 67 | + The name and package arguments work the same as importlib.import_module(). |
| 68 | + In other words, relative module names (with leading dots) work. |
| 69 | +
|
| 70 | + """ |
| 71 | + fullname = resolve_name(name, package) if name.startswith('.') else name |
| 72 | + if fullname not in sys.modules: |
| 73 | + parent_name = fullname.rpartition('.')[0] |
| 74 | + if parent_name: |
| 75 | + parent = __import__(parent_name, fromlist=['__path__']) |
| 76 | + try: |
| 77 | + parent_path = parent.__path__ |
| 78 | + except AttributeError as e: |
| 79 | + raise ModuleNotFoundError( |
| 80 | + f"__path__ attribute not found on {parent_name!r} " |
| 81 | + f"while trying to find {fullname!r}", name=fullname) from e |
| 82 | + else: |
| 83 | + parent_path = None |
| 84 | + return _find_spec(fullname, parent_path) |
| 85 | + else: |
| 86 | + module = sys.modules[fullname] |
| 87 | + if module is None: |
| 88 | + return None |
| 89 | + try: |
| 90 | + spec = module.__spec__ |
| 91 | + except AttributeError: |
| 92 | + raise ValueError('{}.__spec__ is not set'.format(name)) from None |
| 93 | + else: |
| 94 | + if spec is None: |
| 95 | + raise ValueError('{}.__spec__ is None'.format(name)) |
| 96 | + return spec |
| 97 | + |
0 commit comments