Skip to content

refactor: Improve module loading and package detection #942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 32 additions & 19 deletions invoke/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
.. versionadded:: 1.0
"""

POSSIBLE_SUFFIXES = (".py", ".pyc", ".so", ".dll", ".dylib")

# TODO: could introduce config obj here for transmission to Collection
# TODO: otherwise Loader has to know about specific bits to transmit, such
# as auto-dashes, and has to grow one of those for every bit Collection
Expand All @@ -120,33 +122,44 @@
# Lazily determine default CWD if configured value is falsey
return self._start or os.getcwd()

@classmethod
def _is_package(cls, path: str) -> bool:
for suffix in cls.POSSIBLE_SUFFIXES:
if os.path.exists(os.path.join(path, "__init__" + suffix)):
return True
return False

Check warning on line 130 in invoke/loader.py

View check run for this annotation

Codecov / codecov/patch

invoke/loader.py#L130

Added line #L130 was not covered by tests

def find(self, name: str) -> Optional[ModuleSpec]:
debug("FilesystemLoader find starting at {!r}".format(self.start))
spec = None
module = "{}.py".format(name)
modules = [
"{}{}".format(name, suffix) for suffix in self.POSSIBLE_SUFFIXES
]
paths = self.start.split(os.sep)
try:
# walk the path upwards to check for dynamic import
for x in reversed(range(len(paths) + 1)):
path = os.sep.join(paths[0:x])
if module in os.listdir(path):
spec = spec_from_file_location(
name, os.path.join(path, module)
)
break
elif name in os.listdir(path) and os.path.exists(
os.path.join(path, name, "__init__.py")
):
basepath = os.path.join(path, name)
spec = spec_from_file_location(
name,
os.path.join(basepath, "__init__.py"),
submodule_search_locations=[basepath],
)
break
if spec:
debug("Found module: {!r}".format(spec))
return spec
possible_modules = os.listdir(path)
for module in modules:
if module in possible_modules:
spec = spec_from_file_location(
name, os.path.join(path, module)
)
break
elif name in possible_modules and self._is_package(
os.path.join(path, name)
):
basepath = os.path.join(path, name)
spec = spec_from_file_location(
name,
os.path.join(basepath, "__init__.py"),
submodule_search_locations=[basepath],
)
break
if spec:
debug("Found module: {!r}".format(spec))
return spec
except (FileNotFoundError, ModuleNotFoundError):
msg = "ImportError loading {!r}, raising CollectionNotFound"
debug(msg.format(name))
Expand Down