@@ -210,26 +210,22 @@ def _resolve_pkg_root_and_module_name(path: Path) -> tuple[Path, str]:
210210 (missing any __init__.py files) and no valid namespace package root is found.
211211
212212 """
213- pkg_root : Path | None = None
213+ # First, try to find a regular package (with __init__.py files).
214214 pkg_path = _resolve_package_path (path )
215215 if pkg_path is not None :
216216 pkg_root = pkg_path .parent
217+ module_name = _compute_module_name (pkg_root , path )
218+ if module_name :
219+ return pkg_root , module_name
217220
218- # Check for namespace packages by walking up the directory tree.
219- # For each candidate root, compute the module name and verify that Python's
220- # import system would resolve that name to this file.
221- start = pkg_root if pkg_root is not None else path .parent
222- for candidate in (start , * start .parents ):
221+ # No regular package found. Check for namespace packages by walking up the
222+ # directory tree and verifying that Python's import system would resolve
223+ # the computed module name to this file.
224+ for candidate in (path .parent , * path .parent .parents ):
223225 module_name = _compute_module_name (candidate , path )
224226 if module_name and _is_importable (module_name , path ):
225227 # Found a root where Python's import system agrees with our module name.
226- pkg_root = candidate
227- break
228-
229- if pkg_root is not None :
230- module_name = _compute_module_name (pkg_root , path )
231- if module_name :
232- return pkg_root , module_name
228+ return candidate , module_name
233229
234230 msg = f"Could not resolve for { path } "
235231 raise CouldNotResolvePathError (msg )
@@ -270,11 +266,23 @@ def _is_importable(module_name: str, module_path: Path) -> bool:
270266 This verifies that importing `module_name` via Python's standard import mechanism
271267 (as if typed in the REPL) would load the file at `module_path`.
272268
269+ Note: find_spec() has a side effect of creating parent namespace packages in
270+ sys.modules. We clean these up to avoid polluting the module namespace.
273271 """
272+ # Track modules before the call to clean up side effects
273+ modules_before = set (sys .modules .keys ())
274+
274275 try :
275276 spec = importlib .util .find_spec (module_name )
276277 except (ImportError , ValueError , ImportWarning ):
277278 return False
279+ finally :
280+ # Clean up any modules that were added as side effects.
281+ # find_spec() can create parent namespace packages in sys.modules.
282+ modules_added = set (sys .modules .keys ()) - modules_before
283+ for mod_name in modules_added :
284+ sys .modules .pop (mod_name , None )
285+
278286 return _spec_matches_module_path (spec , module_path )
279287
280288
@@ -314,7 +322,11 @@ def _import_module_using_spec(
314322 # Checking with sys.meta_path first in case one of its hooks can import this module,
315323 # such as our own assertion-rewrite hook.
316324 for meta_importer in sys .meta_path :
317- spec = meta_importer .find_spec (module_name , [str (module_location )])
325+ try :
326+ spec = meta_importer .find_spec (module_name , [str (module_location )])
327+ except (ImportError , KeyError , ValueError ):
328+ # Some meta_path finders raise exceptions when parent modules don't exist.
329+ continue
318330 if spec is not None :
319331 break
320332 else :
@@ -323,6 +335,7 @@ def _import_module_using_spec(
323335 mod = importlib .util .module_from_spec (spec )
324336 sys .modules [module_name ] = mod
325337 spec .loader .exec_module (mod ) # type: ignore[union-attr]
338+ _insert_missing_modules (sys .modules , module_name )
326339 return mod
327340
328341 return None
0 commit comments