| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- 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"),
- ]
- )
|