Repository: AngryMaciek/angry-moran-simulator

Issue:
Custom exceptions
* add exceptions file with custom exceptsions as in the `mlem` project

Discussion:
codecov-io: # [Codecov](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25?src=pr&el=h1) Report
> Merging [#25](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25?src=pr&el=desc) (0c291ae) into [master](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/commit/3f82c988f0bb53365081ef437914c0286b200b49?el=desc) (3f82c98) will **decrease** coverage by `1.13%`.
> The diff coverage is `0.00%`.

[![Impacted file tree graph](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25/graphs/tree.svg?width=650&height=150&src=pr&token=V9IFEOWN71)](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##            master      #25      +/-   ##
===========================================
- Coverage   100.00%   98.86%   -1.14%     
===========================================
  Files            5        6       +1     
  Lines          870      880      +10     
===========================================
  Hits           870      870              
- Misses           0       10      +10     
```


| [Impacted Files](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [moranpycess/exceptions.py](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25/diff?src=pr&el=tree#diff-bW9yYW5weWNlc3MvZXhjZXB0aW9ucy5weQ==) | `0.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25?src=pr&el=footer). Last update [3f82c98...0c291ae](https://codecov.io/gh/AngryMaciek/angry-moran-simulator/pull/25?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index db7d90e..b0d5816 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -40,6 +40,7 @@ jobs:
           black --check moranpycess/MoranProcess.py
           black --check moranpycess/MoranProcess2D.py
           black --check moranpycess/MoranProcess3D.py
+          black --check moranpycess/exceptions.py
           black --check tests/unit/context.py
           black --check tests/unit/Individual.py
           black --check tests/unit/MoranProcess.py
@@ -54,6 +55,7 @@ jobs:
           flake8 --max-line-length=101 --ignore F401,E231,W503,E741 moranpycess/MoranProcess.py
           flake8 --max-line-length=101 --ignore F401,E231,W503,E741 moranpycess/MoranProcess2D.py
           flake8 --max-line-length=101 --ignore F401,E231,W503,E741 moranpycess/MoranProcess3D.py
+          flake8 --max-line-length=88 moranpycess/exceptions.py
           flake8 --max-line-length=88 --ignore F401,E402 tests/unit/context.py
           flake8 --max-line-length=88 tests/unit/Individual.py
           flake8 --max-line-length=88 tests/unit/MoranProcess.py
diff --git a/moranpycess/MoranProcess.py b/moranpycess/MoranProcess.py
index 6e96db2..b966519 100644
--- a/moranpycess/MoranProcess.py
+++ b/moranpycess/MoranProcess.py
@@ -114,12 +114,16 @@ def __init__(
                     == TransitionMatrix.shape[1]
                     == len(label_list)
                 )
-                # check if the values are correct
-                for v in np.sum(TransitionMatrix, axis=1):
-                    assert v == 1.0
             except AssertionError as e:
                 e.args += ("Invalid Transition Matrix",)
                 raise
+            # check if the values are correct
+            for v in np.sum(TransitionMatrix, axis=1):
+                if v != 1.0:
+                    raise moranpycess.IncorrectValueError(
+                        parameter="Transition Matrix",
+                        message="Transition probabilities need to add up to 1.0.",
+                    )
         self.TransitionMatrix = copy.deepcopy(TransitionMatrix)
 
     @property
diff --git a/moranpycess/MoranProcess2D.py b/moranpycess/MoranProcess2D.py
index 6012f53..6f84655 100644
--- a/moranpycess/MoranProcess2D.py
+++ b/moranpycess/MoranProcess2D.py
@@ -126,12 +126,16 @@ def __init__(
                     == TransitionMatrix.shape[1]
                     == len(label_list)
                 )
-                # check if the values are correct
-                for v in np.sum(TransitionMatrix, axis=1):
-                    assert v == 1.0
             except AssertionError as e:
                 e.args += ("Invalid Transition Matrix",)
                 raise
+            # check if the values are correct
+            for v in np.sum(TransitionMatrix, axis=1):
+                if v != 1.0:
+                    raise moranpycess.IncorrectValueError(
+                        parameter="Transition Matrix",
+                        message="Transition probabilities need to add up to 1.0.",
+                    )
         self.TransitionMatrix = copy.deepcopy(TransitionMatrix)
 
     @property
diff --git a/moranpycess/MoranProcess3D.py b/moranpycess/MoranProcess3D.py
index 2036c52..8d7c898 100644
--- a/moranpycess/MoranProcess3D.py
+++ b/moranpycess/MoranProcess3D.py
@@ -128,12 +128,16 @@ def __init__(
                     == TransitionMatrix.shape[1]
                     == len(label_list)
                 )
-                # check if the values are correct
-                for v in np.sum(TransitionMatrix, axis=1):
-                    assert v == 1.0
             except AssertionError as e:
                 e.args += ("Invalid Transition Matrix",)
                 raise
+            # check if the values are correct
+            for v in np.sum(TransitionMatrix, axis=1):
+                if v != 1.0:
+                    raise moranpycess.IncorrectValueError(
+                        parameter="Transition Matrix",
+                        message="Transition probabilities need to add up to 1.0.",
+                    )
         self.TransitionMatrix = copy.deepcopy(TransitionMatrix)
 
     @property
diff --git a/moranpycess/__init__.py b/moranpycess/__init__.py
index a1dcf59..e399ea2 100644
--- a/moranpycess/__init__.py
+++ b/moranpycess/__init__.py
@@ -18,3 +18,4 @@
 from .MoranProcess import MoranProcess
 from .MoranProcess2D import MoranProcess2D
 from .MoranProcess3D import MoranProcess3D
+from .exceptions import IncorrectValueError
diff --git a/moranpycess/exceptions.py b/moranpycess/exceptions.py
new file mode 100644
index 0000000..e065e4c
--- /dev/null
+++ b/moranpycess/exceptions.py
@@ -0,0 +1,57 @@
+"""
+##############################################################################
+#
+#   Custom Exceptions
+#
+#   AUTHOR: Maciej_Bak
+#   AFFILIATION: University_of_Basel
+#   AFFILIATION: Swiss_Institute_of_Bioinformatics
+#   CONTACT: wsciekly.maciek@gmail.com
+#   CREATED: 01-04-2021
+#   LICENSE: MIT
+#
+##############################################################################
+"""
+
+
+class Error(Exception):
+    """Base class for other exceptions.
+
+    Args:
+        Exception (Exception): built-in Exception class
+    """
+
+    pass
+
+
+class IncorrectValueError(Error):
+    """Handling incorrect values of user's arguments.
+
+    Args:
+        Error (Error): Base class for other exceptions.
+    """
+
+    def __init__(
+        self,
+        parameter,
+        message="Please check the documentation for expected argument values.",
+    ):
+        """Class initializer.
+
+        Args:
+            parameter (str): parameter name
+            message (str, optional): error message.
+                Defaults to "Please check the documentation
+                for expected argument values.".
+        """
+        self.parameter = parameter
+        self.message = message
+        super().__init__(self.message)
+
+    def __str__(self):
+        """Display the error message.
+
+        Returns:
+            str: error message
+        """
+        return f"Incorrect value for {self.parameter}. {self.message}"


Test patch:
diff --git a/tests/unit/MoranProcess.py b/tests/unit/MoranProcess.py
index 7c8acd1..a987f5a 100644
--- a/tests/unit/MoranProcess.py
+++ b/tests/unit/MoranProcess.py
@@ -213,7 +213,7 @@ def test_classMoranProcessWrongInit(self):
         label_list = ["A", "B", "C"]
         BirthPayoffMatrix = np.array([[10, 20], [30, 40]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess(
                 size_list=size_list,
                 label_list=label_list,
@@ -225,7 +225,7 @@ def test_classMoranProcessWrongInit(self):
         label_list = ["A", "B"]
         BirthPayoffMatrix = np.array([[10, 20, 20], [30, 40, 40], [1, 1, 1]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess(
                 size_list=size_list,
                 label_list=label_list,
@@ -237,7 +237,7 @@ def test_classMoranProcessWrongInit(self):
         label_list = ["A", "B"]
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20, 20], [30, 40, 40], [1, 1, 1]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess(
                 size_list=size_list,
                 label_list=label_list,
@@ -250,7 +250,7 @@ def test_classMoranProcessWrongInit(self):
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20], [30, 40]])
         TransitionMatrix = np.array([[0.0], [0.0]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess(
                 size_list=size_list,
                 label_list=label_list,
@@ -264,7 +264,10 @@ def test_classMoranProcessWrongInit(self):
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20], [30, 40]])
         TransitionMatrix = np.array([[0.5, 0.4], [0.5, 0.5]])
-        with pytest.raises(Exception):
+        expected_error_msg = "Incorrect value for Transition Matrix."
+        expected_error_msg += " "
+        expected_error_msg += "Transition probabilities need to add up to 1.0."
+        with pytest.raises(moranpycess.IncorrectValueError, match=expected_error_msg):
             moranpycess.MoranProcess(
                 size_list=size_list,
                 label_list=label_list,
diff --git a/tests/unit/MoranProcess2D.py b/tests/unit/MoranProcess2D.py
index 8b7cfbf..938416d 100644
--- a/tests/unit/MoranProcess2D.py
+++ b/tests/unit/MoranProcess2D.py
@@ -64,7 +64,7 @@ def test_classMoranProcess2DWrongInit(self):
         grid = np.array([["A", "A"], ["A", "B"]])
         BirthPayoffMatrix = np.array([[10, 20], [30, 40]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess2D(
                 size_list=size_list,
                 label_list=label_list,
@@ -78,7 +78,7 @@ def test_classMoranProcess2DWrongInit(self):
         grid = np.array([["A", "A"], ["A", "B"]])
         BirthPayoffMatrix = np.array([[10, 20, 20], [30, 40, 40], [1, 1, 1]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess2D(
                 size_list=size_list,
                 label_list=label_list,
@@ -92,7 +92,7 @@ def test_classMoranProcess2DWrongInit(self):
         grid = np.array([["A", "A"], ["A", "B"]])
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20, 20], [30, 40, 40], [1, 1, 1]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess2D(
                 size_list=size_list,
                 label_list=label_list,
@@ -106,7 +106,7 @@ def test_classMoranProcess2DWrongInit(self):
         grid = np.array([["A", "A"], ["C", "B"]])
         BirthPayoffMatrix = np.array([[10, 20], [30, 40]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess2D(
                 size_list=size_list,
                 label_list=label_list,
@@ -120,7 +120,7 @@ def test_classMoranProcess2DWrongInit(self):
         grid = np.array([["A", "A"], ["A", "B"]])
         BirthPayoffMatrix = np.array([[10, 20], [30, 40]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess2D(
                 size_list=size_list,
                 label_list=label_list,
@@ -135,7 +135,7 @@ def test_classMoranProcess2DWrongInit(self):
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20], [30, 40]])
         TransitionMatrix = np.array([[0.0], [0.0]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess2D(
                 size_list=size_list,
                 label_list=label_list,
@@ -151,7 +151,10 @@ def test_classMoranProcess2DWrongInit(self):
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20], [30, 40]])
         TransitionMatrix = np.array([[0.5, 0.4], [0.5, 0.5]])
-        with pytest.raises(Exception):
+        expected_error_msg = "Incorrect value for Transition Matrix."
+        expected_error_msg += " "
+        expected_error_msg += "Transition probabilities need to add up to 1.0."
+        with pytest.raises(moranpycess.IncorrectValueError, match=expected_error_msg):
             moranpycess.MoranProcess2D(
                 size_list=size_list,
                 label_list=label_list,
diff --git a/tests/unit/MoranProcess3D.py b/tests/unit/MoranProcess3D.py
index caa5705..8326ce4 100644
--- a/tests/unit/MoranProcess3D.py
+++ b/tests/unit/MoranProcess3D.py
@@ -64,7 +64,7 @@ def test_classMoranProcess3DWrongInit(self):
         grid = np.array([[["A", "A"], ["A", "B"]], [["A", "A"], ["A", "A"]]])
         BirthPayoffMatrix = np.array([[10, 20], [30, 40]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess3D(
                 size_list=size_list,
                 label_list=label_list,
@@ -78,7 +78,7 @@ def test_classMoranProcess3DWrongInit(self):
         grid = np.array([[["A", "A"], ["A", "B"]], [["A", "A"], ["A", "A"]]])
         BirthPayoffMatrix = np.array([[10, 20, 20], [30, 40, 40], [1, 1, 1]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess3D(
                 size_list=size_list,
                 label_list=label_list,
@@ -92,7 +92,7 @@ def test_classMoranProcess3DWrongInit(self):
         grid = np.array([[["A", "A"], ["A", "B"]], [["A", "A"], ["A", "A"]]])
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20, 20], [30, 40, 40], [1, 1, 1]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess3D(
                 size_list=size_list,
                 label_list=label_list,
@@ -106,7 +106,7 @@ def test_classMoranProcess3DWrongInit(self):
         grid = np.array([[["A", "A"], ["C", "B"]], [["A", "A"], ["A", "A"]]])
         BirthPayoffMatrix = np.array([[10, 20], [30, 40]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess3D(
                 size_list=size_list,
                 label_list=label_list,
@@ -120,7 +120,7 @@ def test_classMoranProcess3DWrongInit(self):
         grid = np.array([[["A", "A"], ["B", "B"]], [["A", "A"], ["A", "A"]]])
         BirthPayoffMatrix = np.array([[10, 20], [30, 40]])
         DeathPayoffMatrix = np.array([[1, 2], [3, 4]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess3D(
                 size_list=size_list,
                 label_list=label_list,
@@ -135,7 +135,7 @@ def test_classMoranProcess3DWrongInit(self):
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20], [30, 40]])
         TransitionMatrix = np.array([[0.0], [0.0]])
-        with pytest.raises(Exception):
+        with pytest.raises(AssertionError):
             moranpycess.MoranProcess3D(
                 size_list=size_list,
                 label_list=label_list,
@@ -151,7 +151,10 @@ def test_classMoranProcess3DWrongInit(self):
         BirthPayoffMatrix = np.array([[1, 2], [3, 4]])
         DeathPayoffMatrix = np.array([[10, 20], [30, 40]])
         TransitionMatrix = np.array([[0.5, 0.4], [0.5, 0.5]])
-        with pytest.raises(Exception):
+        expected_error_msg = "Incorrect value for Transition Matrix."
+        expected_error_msg += " "
+        expected_error_msg += "Transition probabilities need to add up to 1.0."
+        with pytest.raises(moranpycess.IncorrectValueError, match=expected_error_msg):
             moranpycess.MoranProcess3D(
                 size_list=size_list,
                 label_list=label_list,