diff --git a/petsctools/__init__.py b/petsctools/__init__.py index a2f1bae..acb869d 100644 --- a/petsctools/__init__.py +++ b/petsctools/__init__.py @@ -11,7 +11,9 @@ from .options import flatten_parameters # noqa: F401 from .utils import PETSC4PY_INSTALLED -# Now conditionally import the functions that depend on petsc4py +# Now conditionally import the functions that depend on petsc4py. If petsc4py +# is not available then attempting to access these attributes will raise an +# informative error. if PETSC4PY_INSTALLED: from .config import get_blas_library # noqa: F401 from .init import ( # noqa: F401 @@ -30,3 +32,32 @@ is_set_from_options, inserted_options, ) +else: + + def __getattr__(name): + petsc4py_attrs = { + "get_blas_library", + "InvalidEnvironmentException", + "InvalidPetscVersionException", + "init", + "get_commandline_options", + "OptionsManager", + "petscobj2str", + "attach_options", + "has_options", + "get_options", + "set_from_options", + "is_set_from_options", + "inserted_options", + } + if name in petsc4py_attrs: + raise ImportError( + f"Cannot load '{name}' from module '{__name__}' because " + "petsc4py is not available.\n" + "If this error appears during pip install then you may have " + "forgotten to pass --no-build-isolation" + ) + else: + raise AttributeError( + f"Module '{__name__}' has no attribute '{name}'" + ) diff --git a/tests/conftest.py b/tests/conftest.py index a75ee2d..f625af9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,10 @@ def pytest_configure(config): + config.addinivalue_line( + "markers", + "skippetsc4py: mark as skipped unless petsc4py is not installed", + ) config.addinivalue_line( "markers", "skipnopetsc4py: mark as skipped unless petsc4py is installed", @@ -17,6 +21,14 @@ def pytest_collection_modifyitems(session, config, items): petsc4py_installed = False for item in items: + if ( + item.get_closest_marker("skippetsc4py") is not None + and petsc4py_installed + ): + item.add_marker( + pytest.mark.skip(reason="Test requires not having petsc4py") + ) + if ( item.get_closest_marker("skipnopetsc4py") is not None and not petsc4py_installed diff --git a/tests/test_no_petsc4py.py b/tests/test_no_petsc4py.py new file mode 100644 index 0000000..cfc7a8e --- /dev/null +++ b/tests/test_no_petsc4py.py @@ -0,0 +1,14 @@ +import petsctools +import pytest + + +@pytest.mark.skippetsc4py +def test_import_error_raised_when_petsc4py_unavailable(): + with pytest.raises(ImportError): + petsctools.init() + + with pytest.raises(ImportError): + petsctools.OptionsManager({}, "prefix") + + with pytest.raises(ImportError): + petsctools.get_blas_library()