diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc76b77..7f7c1d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,4 +34,4 @@ jobs: - name: Test run: | - pytest + PYTHONPATH=. pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbca482..29ef555 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 diff --git a/lazy_loader/__init__.py b/lazy_loader/__init__.py index b1962af..08b7a67 100644 --- a/lazy_loader/__init__.py +++ b/lazy_loader/__init__.py @@ -10,10 +10,33 @@ import os import sys import types +import warnings __all__ = ["attach", "load"] +class DelayedImportWarning(Warning): + pass + + +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 attach(package_name, submodules=None, submod_attrs=None): """Attach lazily loaded submodules, functions, or other attributes. @@ -72,6 +95,14 @@ def __getattr__(name): return importlib.import_module(f"{package_name}.{name}") elif name in attr_to_modules: submod = importlib.import_module(f"{package_name}.{attr_to_modules[name]}") + if name == attr_to_modules[name]: + warnings.warn( + DelayedImportWarning( + f"Module attribute and module have same " + f"name: `{name}`; will likely cause conflicts " + "when accessing attribute." + ) + ) return getattr(submod, name) else: raise AttributeError(f"No {package_name} attribute {name}") @@ -86,24 +117,6 @@ 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. diff --git a/tests/fake_func.py b/tests/fake_func.py new file mode 100644 index 0000000..228fbcf --- /dev/null +++ b/tests/fake_func.py @@ -0,0 +1,2 @@ +def fake_func(): + pass diff --git a/tests/test_lazy_loader.py b/tests/test_lazy_loader.py index f74009f..2817349 100644 --- a/tests/test_lazy_loader.py +++ b/tests/test_lazy_loader.py @@ -94,3 +94,11 @@ def test_lazy_attach(): for k, v in expected.items(): if v is not None: assert locls[k] == v + + +def test_attr_same_as_module(): + __getattr__, __lazy_dir__, __all__ = lazy.attach( + "tests", submod_attrs={"fake_func": ["fake_func"]} + ) + with pytest.warns(lazy.DelayedImportWarning): + __getattr__("fake_func")