Skip to content

Commit

Permalink
Support packages that span multiple directories
Browse files Browse the repository at this point in the history
  • Loading branch information
eltoder committed Mar 24, 2024
1 parent 8f0d153 commit 689fcd3
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 15 deletions.
24 changes: 11 additions & 13 deletions src/slotscheck/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Iterator,
NamedTuple,
Optional,
Tuple,
Union,
)

Expand Down Expand Up @@ -171,10 +172,7 @@ def module_tree(
if spec.submodule_search_locations is None:
tree = Module(name)
else:
assert len(spec.submodule_search_locations) == 1
pkg_location = Path(spec.submodule_search_locations[0])
location = location or pkg_location
tree = _package(name, pkg_location)
tree = _package(name, spec.submodule_search_locations)

if expected_location and location != expected_location:
raise UnexpectedImportLocation(module, expected_location, location)
Expand All @@ -188,27 +186,27 @@ def _add_namespace(tree: ModuleTree, name: ModuleNamePart) -> ModuleTree:

def _submodule(m: pkgutil.ModuleInfo) -> ModuleTree:
if m.ispkg:
[subdir] = m.module_finder.find_spec(
m.name # type: ignore
).submodule_search_locations
return _package(m.name, Path(subdir))
spec = m.module_finder.find_spec(m.name) # type: ignore
assert spec is not None
assert spec.submodule_search_locations is not None
return _package(m.name, spec.submodule_search_locations)
else:
return Module(m.name)


def _is_submodule(m: pkgutil.ModuleInfo, path: AbsPath) -> bool:
return getattr(m.module_finder, "path", "").startswith(str(path))
def _is_submodule(m: pkgutil.ModuleInfo, paths: Tuple[str, ...]) -> bool:
return getattr(m.module_finder, "path", "").startswith(paths)


def _package(name: ModuleNamePart, path: AbsPath) -> Package:
def _package(name: ModuleNamePart, paths: Collection[str]) -> Package:
return Package(
name,
frozenset(
map(
_submodule,
filter(
partial(_is_submodule, path=path),
pkgutil.walk_packages([str(path)]),
partial(_is_submodule, paths=tuple(paths)),
pkgutil.iter_modules(paths),
),
)
),
Expand Down
2 changes: 2 additions & 0 deletions tests/examples/other/implicitly_namespaced/bar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class C:
__slots__ = ()
4 changes: 2 additions & 2 deletions tests/src/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
@pytest.fixture(scope="session", autouse=True)
def add_pypath() -> Iterator[None]:
"Add example modules to the python path"
sys.path.insert(0, str(EXAMPLES_DIR))
sys.path[:0] = [str(EXAMPLES_DIR), str(EXAMPLES_DIR / "other")]
yield
sys.path.remove(str(EXAMPLES_DIR))
del sys.path[:2]


@pytest.fixture(autouse=True)
Expand Down
8 changes: 8 additions & 0 deletions tests/src/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ def test_namespaced(runner: CliRunner):
assert result.output == "All OK!\nScanned 4 module(s), 1 class(es).\n"


def test_implicitly_namespaced(runner: CliRunner):
result = runner.invoke(
cli, ["-m", "implicitly_namespaced"], catch_exceptions=False
)
assert result.exit_code == 0
assert result.output == "All OK!\nScanned 8 module(s), 2 class(es).\n"


def test_multiple_modules(runner: CliRunner):
result = runner.invoke(
cli,
Expand Down
1 change: 1 addition & 0 deletions tests/src/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def test_implicitly_namespaced_submodule(self):
def test_implicitly_namespaced(self):
assert module_tree("implicitly_namespaced", None) == make_pkg(
"implicitly_namespaced",
Module("bar"),
Module("version"),
make_pkg("module", Module("foo"), Module("bla")),
make_pkg("another", Module("foo")),
Expand Down

0 comments on commit 689fcd3

Please sign in to comment.