diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a36d2f1e..e2570637 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -44,7 +44,7 @@ jobs: path: coverage.xml repo_token: ${{ secrets.GITHUB_TOKEN }} pull_request_number: ${{ steps.get-pr.outputs.PR }} - minimum_coverage: 75 + minimum_coverage: 78 show_missing: True fail_below_threshold: True link_missing_lines: True diff --git a/src/pytom_tm/__init__.py b/src/pytom_tm/__init__.py index bf84efe3..056880e0 100644 --- a/src/pytom_tm/__init__.py +++ b/src/pytom_tm/__init__.py @@ -1,7 +1,9 @@ +import logging from importlib import metadata __version__ = metadata.version('pytom-match-pick') + try: import cupy except (ModuleNotFoundError, ImportError): - print('Error for template matching: cupy installation not found or not functional.') + logging.warning('Error for template matching: cupy installation not found or not functional.') diff --git a/src/pytom_tm/extract.py b/src/pytom_tm/extract.py index 4fd9fda3..a8efebf1 100644 --- a/src/pytom_tm/extract.py +++ b/src/pytom_tm/extract.py @@ -17,7 +17,6 @@ plotting_available = False try: - import matplotlib import matplotlib.pyplot as plt import seaborn as sns sns.set(context='talk', style='ticks') diff --git a/src/pytom_tm/plotting.py b/src/pytom_tm/plotting.py index cdbebc7e..6ef44f28 100644 --- a/src/pytom_tm/plotting.py +++ b/src/pytom_tm/plotting.py @@ -5,13 +5,13 @@ from scipy.optimize import curve_fit from scipy.special import erf -if find_spec("matplotlib") is None or find_spec("seaborn") is None: +try: + import matplotlib.pyplot as plt + import seaborn as sns +except ModuleNotFoundError: raise RuntimeError( "ROC estimation can only be done when matplotlib and seaborn are installed." ) -else: - import matplotlib.pyplot as plt - import seaborn as sns sns.set(context="talk", style="ticks") diff --git a/tests/test_broken_imports.py b/tests/test_broken_imports.py new file mode 100644 index 00000000..a4fca053 --- /dev/null +++ b/tests/test_broken_imports.py @@ -0,0 +1,85 @@ +# No imports of pytom_tm outside of the methods +import unittest +from importlib import reload +# Mock out installed dependencies +orig_import = __import__ + +def module_not_found_mock(missing_name): + def import_mock(name, *args): + if name == missing_name: + raise ModuleNotFoundError(f"No module named '{name}'") + return orig_import(name, *args) + return import_mock + +def cupy_import_error_mock(name, *args): + if name == 'cupy': + raise ImportError("Failed to import cupy") + return orig_import(name, *args) + + +class TestMissingDependencies(unittest.TestCase): + + def test_missing_cupy(self): + # assert working import + with self.assertNoLogs(level='WARNING'): + import pytom_tm + cupy_not_found = module_not_found_mock('cupy') + # Test missing cupy + with unittest.mock.patch('builtins.__import__', side_effect=cupy_not_found): + with self.assertLogs(level='WARNING') as cm: + reload(pytom_tm) + self.assertEqual(len(cm.output), 1) + self.assertIn("cupy installation not found or not functional", cm.output[0]) + + def test_broken_cupy(self): + # assert working import + with self.assertNoLogs(level='WARNING'): + import pytom_tm + # Test cupy ImportError + with unittest.mock.patch('builtins.__import__', side_effect=cupy_import_error_mock): + with self.assertLogs(level='WARNING') as cm: + reload(pytom_tm) + self.assertEqual(len(cm.output), 1) + self.assertIn("cupy installation not found or not functional", cm.output[0]) + + def test_missing_matplotlib(self): + # assert working import + import pytom_tm + + matplotlib_not_found = module_not_found_mock('matplotlib.pyplot') + with unittest.mock.patch('builtins.__import__', side_effect=matplotlib_not_found): + with self.assertRaisesRegex(ModuleNotFoundError, 'matplotlib'): + # only pyplot is directly imported so this should be tested + import matplotlib.pyplot as plt + # force reload + # check if we can still import pytom_tm + reload(pytom_tm) + + # check if plotting is indeed disabled after reload + # (reload is needed to prevent python import caching) + self.assertFalse(reload(pytom_tm.template).plotting_available) + self.assertFalse(reload(pytom_tm.extract).plotting_available) + # assert that importing the plotting module fails completely + with self.assertRaisesRegex(RuntimeError, "matplotlib and seaborn"): + reload(pytom_tm.plotting) + + def test_missing_seaborn(self): + # assert working import + import pytom_tm + + seaborn_not_found = module_not_found_mock('seaborn') + with unittest.mock.patch('builtins.__import__', side_effect=seaborn_not_found): + with self.assertRaisesRegex(ModuleNotFoundError, 'seaborn'): + import seaborn + # check if we can still import pytom_tm + reload(pytom_tm) + # check if plotting is indeed disabled + # (reload is needed to prevent python import caching) + self.assertFalse(reload(pytom_tm.template).plotting_available) + self.assertFalse(reload(pytom_tm.extract).plotting_available) + # assert that importing the plotting module fails completely + with self.assertRaisesRegex(RuntimeError, "matplotlib and seaborn"): + reload(pytom_tm.plotting) + + +