|
1 | 1 | # mypy: allow-untyped-defs
|
2 | 2 | import errno
|
| 3 | +import importlib.abc |
| 4 | +import importlib.machinery |
3 | 5 | import os.path
|
4 | 6 | from pathlib import Path
|
5 | 7 | import pickle
|
|
10 | 12 | from typing import Any
|
11 | 13 | from typing import Generator
|
12 | 14 | from typing import Iterator
|
| 15 | +from typing import Optional |
13 | 16 | from typing import Tuple
|
14 | 17 | import unittest.mock
|
15 | 18 |
|
@@ -1120,7 +1123,7 @@ class TestNamespacePackages:
|
1120 | 1123 | """Test import_path support when importing from properly namespace packages."""
|
1121 | 1124 |
|
1122 | 1125 | def setup_directories(
|
1123 |
| - self, tmp_path: Path, monkeypatch: MonkeyPatch, pytester: Pytester |
| 1126 | + self, tmp_path: Path, monkeypatch: Optional[MonkeyPatch], pytester: Pytester |
1124 | 1127 | ) -> Tuple[Path, Path]:
|
1125 | 1128 | # Set up a namespace package "com.company", containing
|
1126 | 1129 | # two subpackages, "app" and "calc".
|
@@ -1149,9 +1152,9 @@ def setup_directories(
|
1149 | 1152 | )
|
1150 | 1153 | )
|
1151 | 1154 | assert r.ret == 0
|
1152 |
| - |
1153 |
| - monkeypatch.syspath_prepend(tmp_path / "src/dist1") |
1154 |
| - monkeypatch.syspath_prepend(tmp_path / "src/dist2") |
| 1155 | + if monkeypatch is not None: |
| 1156 | + monkeypatch.syspath_prepend(tmp_path / "src/dist1") |
| 1157 | + monkeypatch.syspath_prepend(tmp_path / "src/dist2") |
1155 | 1158 | return models_py, algorithms_py
|
1156 | 1159 |
|
1157 | 1160 | @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"])
|
@@ -1235,3 +1238,61 @@ def test_incorrect_namespace_package(
|
1235 | 1238 | tmp_path / "src/dist1/com/company",
|
1236 | 1239 | "app.core.models",
|
1237 | 1240 | )
|
| 1241 | + |
| 1242 | + def test_detect_meta_path( |
| 1243 | + self, |
| 1244 | + tmp_path: Path, |
| 1245 | + monkeypatch: MonkeyPatch, |
| 1246 | + pytester: Pytester, |
| 1247 | + ) -> None: |
| 1248 | + """ |
| 1249 | + resolve_pkg_root_and_module_name() considers sys.meta_path when importing namespace packages. |
| 1250 | +
|
| 1251 | + Regression test for #12112. |
| 1252 | + """ |
| 1253 | + |
| 1254 | + class CustomImporter(importlib.abc.MetaPathFinder): |
| 1255 | + """ |
| 1256 | + Imports the module name "com" as a namespace package. |
| 1257 | +
|
| 1258 | + This ensures our namespace detection considers sys.meta_path, which is important |
| 1259 | + to support all possible ways a module can be imported (for example editable installs). |
| 1260 | + """ |
| 1261 | + |
| 1262 | + def find_spec( |
| 1263 | + self, name: str, path: Any = None, target: Any = None |
| 1264 | + ) -> Optional[importlib.machinery.ModuleSpec]: |
| 1265 | + if name == "com": |
| 1266 | + spec = importlib.machinery.ModuleSpec("com", loader=None) |
| 1267 | + spec.submodule_search_locations = [str(com_root_2), str(com_root_1)] |
| 1268 | + return spec |
| 1269 | + return None |
| 1270 | + |
| 1271 | + # Setup directories without configuring sys.path. |
| 1272 | + models_py, algorithms_py = self.setup_directories( |
| 1273 | + tmp_path, monkeypatch=None, pytester=pytester |
| 1274 | + ) |
| 1275 | + com_root_1 = tmp_path / "src/dist1/com" |
| 1276 | + com_root_2 = tmp_path / "src/dist2/com" |
| 1277 | + |
| 1278 | + # Because the namespace package is not setup correctly, we cannot resolve it as a namespace package. |
| 1279 | + pkg_root, module_name = resolve_pkg_root_and_module_name( |
| 1280 | + models_py, consider_namespace_packages=True |
| 1281 | + ) |
| 1282 | + assert (pkg_root, module_name) == ( |
| 1283 | + tmp_path / "src/dist1/com/company", |
| 1284 | + "app.core.models", |
| 1285 | + ) |
| 1286 | + |
| 1287 | + # Insert our custom importer, which will recognize the "com" directory as a namespace package. |
| 1288 | + new_meta_path = [CustomImporter(), *sys.meta_path] |
| 1289 | + monkeypatch.setattr(sys, "meta_path", new_meta_path) |
| 1290 | + |
| 1291 | + # Now we should be able to resolve the path as namespace package. |
| 1292 | + pkg_root, module_name = resolve_pkg_root_and_module_name( |
| 1293 | + models_py, consider_namespace_packages=True |
| 1294 | + ) |
| 1295 | + assert (pkg_root, module_name) == ( |
| 1296 | + tmp_path / "src/dist1", |
| 1297 | + "com.company.app.core.models", |
| 1298 | + ) |
0 commit comments