core: discovery_pure; allow multiple package roots (#152)

* core: discovery_pure; allow multiple package roots

iterates over my.__path__._path if possible
to discover additional paths to iterate over

else defaults to the path relative to
the current file
This commit is contained in:
Sean Breckenridge 2021-04-02 07:46:18 -07:00 committed by GitHub
parent 5ecd4b4810
commit c31641b4eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -16,7 +16,7 @@ NOT_HPI_MODULE_VAR = '__NOT_HPI_MODULE__'
### ###
import ast import ast
from typing import Optional, Sequence, NamedTuple, Iterable, cast, Any from typing import Optional, Sequence, List, NamedTuple, Iterable, cast, Any
from pathlib import Path from pathlib import Path
import re import re
import logging import logging
@ -114,10 +114,37 @@ def _extract_requirements(a: ast.Module) -> Requires:
# todo should probably be more defensive.. # todo should probably be more defensive..
def all_modules() -> Iterable[HPIModule]: def all_modules() -> Iterable[HPIModule]:
"""
Return all importable modules under all items in the 'my' namespace package
Note: This returns all modules under all roots - if you have
several overlays (multiple items in my.__path__ and you've overridden
modules), this can return multiple HPIModule objects with the same
name. It should respect import order, as we're traversing
in my.__path__ order, so module_by_name should still work
and return the correctly resolved module, but all_modules
can have duplicates
"""
for my_root in _iter_my_roots():
yield from _modules_under_root(my_root)
def _iter_my_roots() -> Iterable[Path]:
import my # doesn't import any code, because of namespace package
paths: List[str] = list(my.__path__) # type: ignore[attr-defined]
if len(paths) == 0:
# should probably never happen?, if this code is running, it was imported
# because something was added to __path__ to match this name
raise RuntimeError("my.__path__ was empty, try re-installing HPI?")
else:
yield from map(Path, paths)
def _modules_under_root(my_root: Path) -> Iterable[HPIModule]:
""" """
Experimental version, which isn't importing the modules, making it more robust and safe. Experimental version, which isn't importing the modules, making it more robust and safe.
""" """
my_root = Path(__file__).absolute().parent.parent
for f in sorted(my_root.rglob('*.py')): for f in sorted(my_root.rglob('*.py')):
if f.is_symlink(): if f.is_symlink():
continue # meh continue # meh
@ -185,8 +212,12 @@ def test_pure() -> None:
""" """
We want to keep this module clean of other HPI imports We want to keep this module clean of other HPI imports
""" """
# this uses string concatenation here to prevent
# these tests from testing against themselves
src = Path(__file__).read_text() src = Path(__file__).read_text()
assert 'import ' + 'my' not in src # 'import my' is allowed, but
# dont allow anything other HPI modules
assert re.findall('import ' + r'my\.\S+', src, re.M) == []
assert 'from ' + 'my' not in src assert 'from ' + 'my' not in src