From da7b020254f860c9d341fa70f3d84d4218bc7aef Mon Sep 17 00:00:00 2001 From: Osma Suominen Date: Mon, 26 Aug 2019 15:15:57 +0300 Subject: [PATCH 1/3] Turn AnnifException into an abstract base class and fix message prefix behavior --- annif/exception.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/annif/exception.py b/annif/exception.py index 867eb2beb..ad3109cd3 100644 --- a/annif/exception.py +++ b/annif/exception.py @@ -1,10 +1,11 @@ """Custom exceptions used by Annif""" +import abc from click import ClickException -class AnnifException(ClickException): +class AnnifException(ClickException, metaclass=abc.ABCMeta): """Base Annif exception. We define this as a subclass of ClickException so that the CLI can automatically handle exceptions.""" @@ -13,22 +14,10 @@ def __init__(self, message, project_id=None, backend_id=None): self.project_id = project_id self.backend_id = backend_id - def format_message(self): - if self.project_id is not None: - return "Project '{}': {}".format(self.project_id, - self.message) - if self.backend_id is not None: - return "Backend '{}': {}".format(self.backend_id, - self.message) - return "Error: {}".format(self.message) - - -class NotInitializedException(AnnifException): - """Exception raised for attempting to use a project or backend that - cannot be initialized, most likely since it is not yet functional - because of lack of vocabulary or training.""" - - prefix = "Couldn't initialize" + @property + @abc.abstractmethod + def prefix(self): + pass def format_message(self): if self.project_id is not None: @@ -42,14 +31,28 @@ def format_message(self): return "{}: {}".format(self.prefix, self.message) +class NotInitializedException(AnnifException): + """Exception raised for attempting to use a project or backend that + cannot be initialized, most likely since it is not yet functional + because of lack of vocabulary or training.""" + + @property + def prefix(self): + return "Couldn't initialize" + + class ConfigurationException(AnnifException): """Exception raised when a project or backend is misconfigured.""" - prefix = "Misconfigured" + @property + def prefix(self): + return "Misconfigured" class NotSupportedException(AnnifException): """Exception raised when an operation is not supported by a project or backend.""" - prefix = "Not supported" + @property + def prefix(self): + return "Not supported" From 0ecacf4130822e59e4d52aa31f8a368d1730e78f Mon Sep 17 00:00:00 2001 From: Osma Suominen Date: Tue, 27 Aug 2019 09:40:09 +0300 Subject: [PATCH 2/3] Workaround for making AnnifException abstract (non-instantiable), since Exception subclasses cannot be made abstract using the abc module. --- annif/exception.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/annif/exception.py b/annif/exception.py index ad3109cd3..9acbd052b 100644 --- a/annif/exception.py +++ b/annif/exception.py @@ -1,23 +1,24 @@ """Custom exceptions used by Annif""" -import abc from click import ClickException -class AnnifException(ClickException, metaclass=abc.ABCMeta): +class AnnifException(ClickException): """Base Annif exception. We define this as a subclass of ClickException so - that the CLI can automatically handle exceptions.""" + that the CLI can automatically handle exceptions. This exception cannot be + instantiated directly - subclasses should be used instead.""" def __init__(self, message, project_id=None, backend_id=None): super().__init__(message) self.project_id = project_id self.backend_id = backend_id - @property - @abc.abstractmethod - def prefix(self): - pass + if self.prefix is None: + raise TypeError("Cannot instantiate exception without a prefix.") + + # subclasses should set this to a descriptive prefix + prefix = None def format_message(self): if self.project_id is not None: @@ -36,23 +37,17 @@ class NotInitializedException(AnnifException): cannot be initialized, most likely since it is not yet functional because of lack of vocabulary or training.""" - @property - def prefix(self): - return "Couldn't initialize" + prefix = "Couldn't initialize" class ConfigurationException(AnnifException): """Exception raised when a project or backend is misconfigured.""" - @property - def prefix(self): - return "Misconfigured" + prefix = "Misconfigured" class NotSupportedException(AnnifException): """Exception raised when an operation is not supported by a project or backend.""" - @property - def prefix(self): - return "Not supported" + prefix = "Not supported" From 475bba05c8df96703d586611415e16b74ed0f279 Mon Sep 17 00:00:00 2001 From: Osma Suominen Date: Tue, 27 Aug 2019 10:08:21 +0300 Subject: [PATCH 3/3] Add unit tests for exception module --- tests/test_exception.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/test_exception.py diff --git a/tests/test_exception.py b/tests/test_exception.py new file mode 100644 index 000000000..3fda48b5b --- /dev/null +++ b/tests/test_exception.py @@ -0,0 +1,22 @@ +"""Unit tests for Annif exception classes""" + +import pytest +from annif.exception import AnnifException +from click import ClickException + + +def test_annifexception_not_instantiable(): + with pytest.raises(TypeError): + exc = AnnifException("test message") + + +def test_annifexception_is_clickexception(): + + # we need to define a custom class to make an instantiable exception + class CustomException(AnnifException): + @property + def prefix(self): + return "my prefix" + + exc = CustomException("test message") + assert isinstance(exc, ClickException)