From fd8b9a798be7b0fb7620c35d9c8f54e3a0473133 Mon Sep 17 00:00:00 2001 From: Osma Suominen Date: Thu, 21 Nov 2024 17:14:44 +0200 Subject: [PATCH 1/2] smarter initialization of optional analyzers; use importlib to test optional feature availability --- annif/analyzer/__init__.py | 24 +++++++----------------- annif/analyzer/analyzer.py | 5 +++++ annif/analyzer/spacy.py | 7 +++++++ annif/analyzer/voikko.py | 10 ++++++++-- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/annif/analyzer/__init__.py b/annif/analyzer/__init__.py index 27a2cd792..81f525116 100644 --- a/annif/analyzer/__init__.py +++ b/annif/analyzer/__init__.py @@ -8,7 +8,7 @@ import annif from annif.util import parse_args -from . import simple, simplemma, snowball +from . import simple, simplemma, snowball, spacy, voikko if TYPE_CHECKING: from annif.analyzer.analyzer import Analyzer @@ -17,7 +17,10 @@ def register_analyzer(analyzer): - _analyzers[analyzer.name] = analyzer + if analyzer.is_available(): + _analyzers[analyzer.name] = analyzer + else: + annif.logger.debug(f"{analyzer.name} analyzer not available, not enabling it") def get_analyzer(analyzerspec: str) -> Analyzer: @@ -37,18 +40,5 @@ def get_analyzer(analyzerspec: str) -> Analyzer: register_analyzer(simple.SimpleAnalyzer) register_analyzer(snowball.SnowballAnalyzer) register_analyzer(simplemma.SimplemmaAnalyzer) - -# Optional analyzers -try: - from . import voikko - - register_analyzer(voikko.VoikkoAnalyzer) -except ImportError: - annif.logger.debug("voikko not available, not enabling voikko analyzer") - -try: - from . import spacy - - register_analyzer(spacy.SpacyAnalyzer) -except ImportError: - annif.logger.debug("spaCy not available, not enabling spacy analyzer") +register_analyzer(voikko.VoikkoAnalyzer) +register_analyzer(spacy.SpacyAnalyzer) diff --git a/annif/analyzer/analyzer.py b/annif/analyzer/analyzer.py index 129b882ab..612234d95 100644 --- a/annif/analyzer/analyzer.py +++ b/annif/analyzer/analyzer.py @@ -22,6 +22,11 @@ class Analyzer(metaclass=abc.ABCMeta): name = None token_min_length = 3 # default value, can be overridden in instances + @staticmethod + def is_available() -> bool: + """Return True if the analyzer is available for use, False if not.""" + return True # can be overridden in implementations if necessary + def __init__(self, **kwargs) -> None: if _KEY_TOKEN_MIN_LENGTH in kwargs: self.token_min_length = int(kwargs[_KEY_TOKEN_MIN_LENGTH]) diff --git a/annif/analyzer/spacy.py b/annif/analyzer/spacy.py index 184f03ffc..407f0d9c7 100644 --- a/annif/analyzer/spacy.py +++ b/annif/analyzer/spacy.py @@ -2,6 +2,8 @@ from __future__ import annotations +import importlib + import annif.util from annif.exception import OperationFailedException @@ -13,6 +15,11 @@ class SpacyAnalyzer(analyzer.Analyzer): name = "spacy" + @staticmethod + def is_available() -> bool: + # return True iff spaCy is installed + return importlib.util.find_spec("spacy") is not None + def __init__(self, param: str, **kwargs) -> None: import spacy diff --git a/annif/analyzer/voikko.py b/annif/analyzer/voikko.py index b3e7d5007..3722d6b4b 100644 --- a/annif/analyzer/voikko.py +++ b/annif/analyzer/voikko.py @@ -3,8 +3,7 @@ from __future__ import annotations import functools - -import voikko.libvoikko +import importlib from . import analyzer @@ -12,6 +11,11 @@ class VoikkoAnalyzer(analyzer.Analyzer): name = "voikko" + @staticmethod + def is_available() -> bool: + # return True iff Voikko is installed + return importlib.util.find_spec("voikko") is not None + def __init__(self, param: str, **kwargs) -> None: self.param = param self.voikko = None @@ -26,6 +30,8 @@ def __getstate__(self) -> dict[str, str | None]: @functools.lru_cache(maxsize=500000) def _normalize_word(self, word: str) -> str: + import voikko.libvoikko + if self.voikko is None: self.voikko = voikko.libvoikko.Voikko(self.param) result = self.voikko.analyze(word) From 419f8df671f2640332d231cd76a0399561e4a448 Mon Sep 17 00:00:00 2001 From: Osma Suominen Date: Thu, 21 Nov 2024 17:28:33 +0200 Subject: [PATCH 2/2] fix test skips for voikko and spacy analyzers --- tests/test_analyzer_spacy.py | 5 ++++- tests/test_analyzer_voikko.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_analyzer_spacy.py b/tests/test_analyzer_spacy.py index bf14a9ae0..e3012af05 100644 --- a/tests/test_analyzer_spacy.py +++ b/tests/test_analyzer_spacy.py @@ -3,9 +3,12 @@ import pytest import annif.analyzer +import annif.analyzer.spacy from annif.exception import OperationFailedException -spacy = pytest.importorskip("spacy") +pytestmark = pytest.mark.skipif( + not annif.analyzer.spacy.SpacyAnalyzer.is_available(), reason="spaCy is required" +) def test_spacy_model_not_found(): diff --git a/tests/test_analyzer_voikko.py b/tests/test_analyzer_voikko.py index 6cba156f7..8598482b1 100644 --- a/tests/test_analyzer_voikko.py +++ b/tests/test_analyzer_voikko.py @@ -3,8 +3,11 @@ import pytest import annif.analyzer +import annif.analyzer.voikko -voikko = pytest.importorskip("annif.analyzer.voikko") +pytestmark = pytest.mark.skipif( + not annif.analyzer.voikko.VoikkoAnalyzer.is_available(), reason="voikko is required" +) def test_voikko_getstate():