import importlib import os import subprocess import sys import types from unittest import mock import pytest import lazy_loader as lazy def test_lazy_import_basics(): math = lazy.load("math") anything_not_real = lazy.load("anything_not_real") # Now test that accessing attributes does what it should assert math.sin(math.pi) == pytest.approx(0, 1e-6) # poor-mans pytest.raises for testing errors on attribute access try: anything_not_real.pi raise AssertionError() # Should not get here except ModuleNotFoundError: pass assert isinstance(anything_not_real, lazy.DelayedImportErrorModule) # see if it changes for second access try: anything_not_real.pi raise AssertionError() # Should not get here except ModuleNotFoundError: pass def test_lazy_import_subpackages(): with pytest.warns(RuntimeWarning): hp = lazy.load("html.parser") assert "html" in sys.modules assert type(sys.modules["html"]) == type(pytest) assert isinstance(hp, importlib.util._LazyModule) assert "html.parser" in sys.modules assert sys.modules["html.parser"] == hp def test_lazy_import_impact_on_sys_modules(): math = lazy.load("math") anything_not_real = lazy.load("anything_not_real") assert isinstance(math, types.ModuleType) assert "math" in sys.modules assert isinstance(anything_not_real, lazy.DelayedImportErrorModule) assert "anything_not_real" not in sys.modules # only do this if numpy is installed pytest.importorskip("numpy") np = lazy.load("numpy") assert isinstance(np, types.ModuleType) assert "numpy" in sys.modules np.pi # trigger load of numpy assert isinstance(np, types.ModuleType) assert "numpy" in sys.modules def test_lazy_import_nonbuiltins(): np = lazy.load("numpy") sp = lazy.load("scipy") if not isinstance(np, lazy.DelayedImportErrorModule): assert np.sin(np.pi) == pytest.approx(0, 1e-6) if isinstance(sp, lazy.DelayedImportErrorModule): try: sp.pi raise AssertionError() except ModuleNotFoundError: pass def test_lazy_attach(): name = "mymod" submods = ["mysubmodule", "anothersubmodule"] myall = {"not_real_submod": ["some_var_or_func"]} locls = { "attach": lazy.attach, "name": name, "submods": submods, "myall": myall, } s = "__getattr__, __lazy_dir__, __all__ = attach(name, submods, myall)" exec(s, {}, locls) expected = { "attach": lazy.attach, "name": name, "submods": submods, "myall": myall, "__getattr__": None, "__lazy_dir__": None, "__all__": None, } assert locls.keys() == expected.keys() for k, v in expected.items(): if v is not None: assert locls[k] == v def test_attach_same_module_and_attr_name(): from lazy_loader.tests import fake_pkg # Grab attribute twice, to ensure that importing it does not # override function by module assert isinstance(fake_pkg.some_func, types.FunctionType) assert isinstance(fake_pkg.some_func, types.FunctionType) # Ensure imports from submodule still work from lazy_loader.tests.fake_pkg.some_func import some_func assert isinstance(some_func, types.FunctionType) FAKE_STUB = """ from . import rank from ._gaussian import gaussian from .edges import sobel, scharr, prewitt, roberts """ def test_stub_loading(tmp_path): stub = tmp_path / "stub.pyi" stub.write_text(FAKE_STUB) _get, _dir, _all = lazy.attach_stub("my_module", str(stub)) expect = {"gaussian", "sobel", "scharr", "prewitt", "roberts", "rank"} assert set(_dir()) == set(_all) == expect def test_stub_loading_parity(): from lazy_loader.tests import fake_pkg from_stub = lazy.attach_stub(fake_pkg.__name__, fake_pkg.__file__) stub_getter, stub_dir, stub_all = from_stub assert stub_all == fake_pkg.__all__ assert stub_dir() == fake_pkg.__lazy_dir__() assert stub_getter("some_func") == fake_pkg.some_func def test_stub_loading_errors(tmp_path): stub = tmp_path / "stub.pyi" stub.write_text("from ..mod import func\n") with pytest.raises(ValueError, match="Only within-module imports are supported"): lazy.attach_stub("name", str(stub)) with pytest.raises(ValueError, match="Cannot load imports from non-existent stub"): lazy.attach_stub("name", "not a file") stub2 = tmp_path / "stub2.pyi" stub2.write_text("from .mod import *\n") with pytest.raises(ValueError, match=".*does not support star import"): lazy.attach_stub("name", str(stub2)) def test_require_kwarg(): have_importlib_metadata = importlib.util.find_spec("importlib.metadata") is not None dot = "." if have_importlib_metadata else "_" # Test with a module that definitely exists, behavior hinges on requirement with mock.patch(f"importlib{dot}metadata.version") as version: version.return_value = "1.0.0" math = lazy.load("math", require="somepkg >= 2.0") assert isinstance(math, lazy.DelayedImportErrorModule) math = lazy.load("math", require="somepkg >= 1.0") assert math.sin(math.pi) == pytest.approx(0, 1e-6) # We can fail even after a successful import math = lazy.load("math", require="somepkg >= 2.0") assert isinstance(math, lazy.DelayedImportErrorModule) # When a module can be loaded but the version can't be checked, # raise a ValueError with pytest.raises(ValueError): lazy.load("math", require="somepkg >= 1.0") def test_parallel_load(): pytest.importorskip("numpy") subprocess.run( [ sys.executable, os.path.join(os.path.dirname(__file__), "import_np_parallel.py"), ] )