Skip to content

Commit f14e5df

Browse files
committed
Replace custom import sequence with MetaPathFinder
This should address review comments and increase the robustness of the solution.
1 parent b22169e commit f14e5df

File tree

1 file changed

+28
-28
lines changed

1 file changed

+28
-28
lines changed

src/pyproject_hooks/_in_process/_in_process.py

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import traceback
2222
from glob import glob
2323
from importlib import import_module
24-
from importlib.util import module_from_spec
2524
from importlib.machinery import PathFinder
2625
from os.path import join as pjoin
2726

@@ -65,40 +64,41 @@ def _build_backend():
6564

6665
if backend_path:
6766
extra_pathitems = backend_path.split(os.pathsep)
68-
obj = _load_module_from_path(mod_path, extra_pathitems)
69-
else:
70-
try:
71-
obj = import_module(mod_path)
72-
except ImportError:
73-
msg = f"Cannot import {mod_path!r}"
74-
raise BackendUnavailable(msg, traceback.format_exc())
67+
sys.meta_path.insert(0, _BackendPathFinder(extra_pathitems, mod_path))
68+
69+
try:
70+
obj = import_module(mod_path)
71+
except ImportError:
72+
msg = f"Cannot import {mod_path!r}"
73+
raise BackendUnavailable(msg, traceback.format_exc())
7574

7675
if obj_path:
7776
for path_part in obj_path.split("."):
7877
obj = getattr(obj, path_part)
7978
return obj
8079

8180

82-
def _load_module_from_path(fullname, pathitems):
83-
"""Given a set of sys.path-like entries, load a module from it"""
84-
sys.path[:0] = pathitems # Still required for other imports.
85-
parts = fullname.split(".")
86-
# Parent packages need to be imported to ensure everything comes from pathitems.
87-
for i in range(len(parts)):
88-
module_name = ".".join(parts[: i + 1])
89-
spec = _find_spec_in_path(module_name, pathitems)
90-
module = module_from_spec(spec)
91-
sys.modules[module_name] = module
92-
spec.loader.exec_module(module)
93-
return module
94-
95-
96-
def _find_spec_in_path(fullname, pathitems):
97-
"""Given sys.path-like entries, find a module spec or raise an exception"""
98-
spec = PathFinder.find_spec(fullname, path=pathitems)
99-
if not spec:
100-
raise BackendUnavailable(f"Cannot find module {fullname!r} in {pathitems!r}")
101-
return spec
81+
class _BackendPathFinder:
82+
"""Implements the MetaPathFinder interface to locate modules in ``backend-path``.
83+
84+
Since the environment provided by the frontend can contain all sorts of
85+
MetaPathFinders, the only way to ensure the backend is loaded from the
86+
right place is to prepend our own.
87+
"""
88+
89+
def __init__(self, backend_path, backend_module):
90+
self.backend_path = backend_path
91+
self.backend_module = backend_module
92+
93+
def find_spec(self, fullname, _path, _target=None):
94+
# Ignore other items in _path or sys.path and use backend_path instead:
95+
spec = PathFinder.find_spec(fullname, path=self.backend_path)
96+
if spec is None and fullname == self.backend_module:
97+
# According to the spec, the backend MUST be loaded from backend-path.
98+
# Therefore, we can halt the import machinery and raise a clean error.
99+
msg = f"Cannot find module {self.backend_module!r} in {self.backend_path!r}"
100+
raise BackendUnavailable(msg)
101+
return spec
102102

103103

104104
def _supported_features():

0 commit comments

Comments
 (0)