From d54c31cdc598c2c373b66c9c527f5de586a87368 Mon Sep 17 00:00:00 2001 From: Daniel <d.hornung@indiscale.com> Date: Mon, 17 Jun 2024 15:57:53 +0200 Subject: [PATCH] ENH: Also hande the case where spss is not installed. --- src/caoscrawler/__init__.py | 9 +++++++-- src/caoscrawler/utils.py | 30 ++++++++++++++++++++++++++++++ unittests/test_utilities.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/caoscrawler/__init__.py b/src/caoscrawler/__init__.py index 3c71caed..41b96323 100644 --- a/src/caoscrawler/__init__.py +++ b/src/caoscrawler/__init__.py @@ -1,5 +1,10 @@ -from . import converters -from .conv_impl.spss import SPSSConverter +from . import converters, utils +try: + from .conv_impl.spss import SPSSConverter +except ImportError as err: + SPSSConverter: type = utils.MissingImport( + name="SPSSConverter", hint="Try installing with the `spss` extra option.", + err=err) from .crawl import Crawler, SecurityMode from .version import CfoodRequiredVersionError, get_caoscrawler_version diff --git a/src/caoscrawler/utils.py b/src/caoscrawler/utils.py index c62f44ee..096fde9b 100644 --- a/src/caoscrawler/utils.py +++ b/src/caoscrawler/utils.py @@ -25,6 +25,9 @@ # Some utility functions, e.g. for extending pylib. +import sys +from typing import Optional + import linkahead as db @@ -39,3 +42,30 @@ def has_parent(entity: db.Entity, name: str): if parent.name == name: return True return False + + +def MissingImport(name: str, hint: str = "", err: Optional[Exception] = None) -> type: + """Factory with dummy classes, which may be assigned to variables but never used.""" + def _error(): + error_msg = f"This class ({name}) cannot be used, because some libraries are missing." + if hint: + error_msg += "\n\n" + hint + + if err: + print(error_msg, file=sys.stdout) + raise RuntimeError(error_msg) from err + raise RuntimeError(error_msg) + + class _Meta(type): + def __getattribute__(cls, *args, **kwargs): + _error() + + def __call__(cls, *args, **kwargs): + _error() + + class _DummyClass(metaclass=_Meta): + pass + + _DummyClass.__name__ = name + + return _DummyClass diff --git a/unittests/test_utilities.py b/unittests/test_utilities.py index 5a80ab9b..dfb79c8b 100644 --- a/unittests/test_utilities.py +++ b/unittests/test_utilities.py @@ -19,7 +19,10 @@ # along with this program. If not, see <https://www.gnu.org/licenses/>. # +import pytest + from caoscrawler.crawl import split_restricted_path +from caoscrawler.utils import MissingImport def test_split_restricted_path(): @@ -33,3 +36,33 @@ def test_split_restricted_path(): assert split_restricted_path("/test//bla") == ["test", "bla"] assert split_restricted_path("//test/bla") == ["test", "bla"] assert split_restricted_path("///test//bla////") == ["test", "bla"] + + +def test_dummy_class(): + Missing = MissingImport(name="Not Important", hint="Do the thing instead.") + with pytest.raises(RuntimeError) as err_info_1: + print(Missing.__name__) + with pytest.raises(RuntimeError) as err_info_2: + Missing() + with pytest.raises(RuntimeError) as err_info_3: + print(Missing.foo) + + for err_info in (err_info_1, err_info_2, err_info_3): + msg = str(err_info.value) + assert "(Not Important)" in msg + assert msg.endswith("Do the thing instead.") + + MissingErr = MissingImport(name="Not Important", hint="Do the thing instead.", + err=ImportError("Old error")) + with pytest.raises(RuntimeError) as err_info_1: + print(MissingErr.__name__) + with pytest.raises(RuntimeError) as err_info_2: + MissingErr() + with pytest.raises(RuntimeError) as err_info_3: + print(MissingErr.foo) + + for err_info in (err_info_1, err_info_2, err_info_3): + msg = str(err_info.value) + assert "(Not Important)" in msg + orig_msg = str(err_info.value.__cause__) + assert orig_msg == "Old error" -- GitLab