Skip to content

Commit

Permalink
Merge pull request #4 from stefanv/better-error
Browse files Browse the repository at this point in the history
Simplify delayed exception handling and improve reporting
  • Loading branch information
stefanv authored Mar 8, 2022
2 parents ab3d7df + 6313b05 commit 16fc18e
Showing 1 changed file with 33 additions and 24 deletions.
57 changes: 33 additions & 24 deletions lazy_loader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
"""
import importlib
import importlib.util
import inspect
import os
import sys
import types

__all__ = ["attach", "load"]


def attach(package_name, submodules=None, submod_attrs=None):
"""Attach lazily loaded submodules, functions, or other attributes.
Expand Down Expand Up @@ -83,6 +86,24 @@ def __dir__():
return __getattr__, __dir__, list(__all__)


class DelayedImportErrorModule(types.ModuleType):
def __init__(self, frame_data, *args, **kwargs):
self.__frame_data = frame_data
super().__init__(*args, **kwargs)

def __getattr__(self, x):
if x in ("__class__", "__file__", "__frame_data"):
super().__getattr__(x)
else:
fd = self.__frame_data
raise ModuleNotFoundError(
f"No module named '{fd['spec']}'\n\n"
"This error is lazily reported, having originally occured in\n"
f' File {fd["filename"]}, line {fd["lineno"]}, in {fd["function"]}\n\n'
f'----> {"".join(fd["code_context"]).strip()}'
)


def load(fullname, error_on_import=False):
"""Return a lazily imported proxy for a module.
Expand Down Expand Up @@ -138,13 +159,18 @@ def myfunc():
if error_on_import:
raise ModuleNotFoundError(f"No module named '{fullname}'")
else:
spec = importlib.util.spec_from_loader(fullname, loader=None)
module = importlib.util.module_from_spec(spec)
tmp_loader = importlib.machinery.SourceFileLoader(module, path=None)
loader = DelayedImportErrorLoader(tmp_loader)
loader.exec_module(module)
# dont add to sys.modules. The module wasn't found.
return module
try:
parent = inspect.stack()[1]
frame_data = {
"spec": fullname,
"filename": parent.filename,
"lineno": parent.lineno,
"function": parent.function,
"code_context": parent.code_context,
}
return DelayedImportErrorModule(frame_data, "DelayedImportErrorModule")
finally:
del parent

module = importlib.util.module_from_spec(spec)
sys.modules[fullname] = module
Expand All @@ -153,20 +179,3 @@ def myfunc():
loader.exec_module(module)

return module


class DelayedImportErrorLoader(importlib.util.LazyLoader):
def exec_module(self, module):
super().exec_module(module)
module.__class__ = DelayedImportErrorModule


class DelayedImportErrorModule(types.ModuleType):
def __getattribute__(self, attr):
"""Trigger a ModuleNotFoundError upon attribute access"""
spec = super().__getattribute__("__spec__")
# allows isinstance and type functions to work without raising error
if attr in ["__class__"]:
return super().__getattribute__("__class__")

raise ModuleNotFoundError(f"No module named '{spec.name}'")

0 comments on commit 16fc18e

Please # to comment.