From a161d78a610bb365bc96acd2b8422218728a05b2 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Wed, 25 Jun 2025 12:02:29 +0100 Subject: [PATCH 1/9] Fail nicely when trying to use petsc4py methods without petsc4py available --- petsctools/__init__.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/petsctools/__init__.py b/petsctools/__init__.py index ea2a920..1d444e0 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 @@ -20,3 +22,18 @@ init, ) from .options import OptionsManager # noqa: F401 +else: + def __getattr__(name): + petsc4py_attrs = { + "get_blas_library", + "InvalidEnvironmentException", + "InvalidPetscVersionException", + "init", + "OptionsManager", + } + if name in petsc4py_attrs: + raise ImportError( + f"Cannot load '{name}' from module '{__name__}' because petsc4py " + "is not available" + ) + raise AttributeError(f"Module '{__name__}' has no attribute '{name}'") From 7c1e04ce243c9f1f3a5f99ab0550bc0a956c8370 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 4 Jul 2025 14:51:19 +0100 Subject: [PATCH 2/9] Linting --- petsctools/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/petsctools/__init__.py b/petsctools/__init__.py index 1d444e0..a644679 100644 --- a/petsctools/__init__.py +++ b/petsctools/__init__.py @@ -33,7 +33,7 @@ def __getattr__(name): } if name in petsc4py_attrs: raise ImportError( - f"Cannot load '{name}' from module '{__name__}' because petsc4py " - "is not available" + f"Cannot load '{name}' from module '{__name__}' because " + "petsc4py is not available" ) raise AttributeError(f"Module '{__name__}' has no attribute '{name}'") From f3485914dcb4ce895f1910dd082b82713285ef52 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 4 Jul 2025 14:53:37 +0100 Subject: [PATCH 3/9] fixup --- petsctools/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/petsctools/__init__.py b/petsctools/__init__.py index dc4c3e6..8918d85 100644 --- a/petsctools/__init__.py +++ b/petsctools/__init__.py @@ -54,4 +54,5 @@ def __getattr__(name): f"Cannot load '{name}' from module '{__name__}' because " "petsc4py is not available" ) - raise AttributeError(f"Module '{__name__}' has no attribute '{name}'") + else: + raise AttributeError(f"Module '{__name__}' has no attribute '{name}'") From 1ecf7950dfc79bb874b5febaabaf44e6ac26487f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 4 Jul 2025 14:59:58 +0100 Subject: [PATCH 4/9] Add tests --- tests/conftest.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index a75ee2d..2a3b014 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,12 @@ 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 From bfc4c5544949a0b8f42d17ea84da53dbe0af78aa Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 4 Jul 2025 15:00:19 +0100 Subject: [PATCH 5/9] whoops --- tests/test_no_petsc4py.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/test_no_petsc4py.py 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() From 741c1f0a880c0ebc5929b56e96ccb563df616787 Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Fri, 4 Jul 2025 15:48:09 +0100 Subject: [PATCH 6/9] linting --- petsctools/__init__.py | 5 ++++- tests/conftest.py | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/petsctools/__init__.py b/petsctools/__init__.py index 8918d85..253ed45 100644 --- a/petsctools/__init__.py +++ b/petsctools/__init__.py @@ -33,6 +33,7 @@ inserted_options, ) else: + def __getattr__(name): petsc4py_attrs = { "get_blas_library", @@ -55,4 +56,6 @@ def __getattr__(name): "petsc4py is not available" ) else: - raise AttributeError(f"Module '{__name__}' has no attribute '{name}'") + raise AttributeError( + f"Module '{__name__}' has no attribute '{name}'" + ) diff --git a/tests/conftest.py b/tests/conftest.py index 2a3b014..f625af9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,7 +25,9 @@ def pytest_collection_modifyitems(session, config, items): item.get_closest_marker("skippetsc4py") is not None and petsc4py_installed ): - item.add_marker(pytest.mark.skip(reason="Test requires not having petsc4py")) + item.add_marker( + pytest.mark.skip(reason="Test requires not having petsc4py") + ) if ( item.get_closest_marker("skipnopetsc4py") is not None From b9fceefe1ab79dd0e6a271786b4e8dded405a59f Mon Sep 17 00:00:00 2001 From: Connor Ward Date: Mon, 14 Jul 2025 16:20:51 +0100 Subject: [PATCH 7/9] Update petsctools/__init__.py Co-authored-by: Josh Hope-Collins --- petsctools/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/petsctools/__init__.py b/petsctools/__init__.py index 253ed45..b3b0184 100644 --- a/petsctools/__init__.py +++ b/petsctools/__init__.py @@ -53,7 +53,9 @@ def __getattr__(name): if name in petsc4py_attrs: raise ImportError( f"Cannot load '{name}' from module '{__name__}' because " - "petsc4py is not available" + "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( From 040e5060f59d7ff60007a4f514e803e7fc04a1de Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Mon, 14 Jul 2025 16:57:58 +0100 Subject: [PATCH 8/9] erroneous dot --- petsctools/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/petsctools/__init__.py b/petsctools/__init__.py index b3b0184..d851126 100644 --- a/petsctools/__init__.py +++ b/petsctools/__init__.py @@ -55,7 +55,7 @@ def __getattr__(name): 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". + "forgotten to pass --no-build-isolation" ) else: raise AttributeError( From 4c2ebe4d0a3ca90ad2ab27fb3b2bbfca232540f1 Mon Sep 17 00:00:00 2001 From: Josh Hope-Collins Date: Mon, 14 Jul 2025 16:59:09 +0100 Subject: [PATCH 9/9] missing space --- petsctools/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/petsctools/__init__.py b/petsctools/__init__.py index d851126..acb869d 100644 --- a/petsctools/__init__.py +++ b/petsctools/__init__.py @@ -54,7 +54,7 @@ def __getattr__(name): 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" + "If this error appears during pip install then you may have " "forgotten to pass --no-build-isolation" ) else: