From ca4606780901843bb657928846de6f01c92726da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:49:57 +0200 Subject: [PATCH 01/77] Implement OutOfBoundsError --- src/safeds/exceptions/__init__.py | 3 ++ src/safeds/exceptions/_generic.py | 66 +++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/safeds/exceptions/_generic.py diff --git a/src/safeds/exceptions/__init__.py b/src/safeds/exceptions/__init__.py index 05bf9dcf8..41baa46d5 100644 --- a/src/safeds/exceptions/__init__.py +++ b/src/safeds/exceptions/__init__.py @@ -13,6 +13,7 @@ ValueNotPresentWhenFittedError, WrongFileExtensionError, ) +from safeds.exceptions._generic import OutOfBoundsError from safeds.exceptions._ml import ( DatasetContainsTargetError, DatasetMissesDataError, @@ -24,6 +25,8 @@ ) __all__ = [ + # Generic exceptions + "OutOfBoundsError", # Data exceptions "ColumnLengthMismatchError", "ColumnSizeError", diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py new file mode 100644 index 000000000..d35cd2bd3 --- /dev/null +++ b/src/safeds/exceptions/_generic.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod + + +class OutOfBoundsError(ValueError): + """ + A generic exception to signal that a (float) value is out of bounds. + + Parameters + ---------- + actual: float + The actual value. + lower_bound: _Bound | None + The lower Bound. + upper_bound: _Bound | None + The lower Bound. + """ + + def __init__(self, actual: float, *, lower_bound: _Bound | None = None, upper_bound: _Bound | None = None): + if lower_bound is None and upper_bound is None: + raise NotImplementedError("Value cannot be out of bounds if there are no bounds.") + super().__init__(f"{actual} is not inside {lower_bound._str_lower_bound()}, {upper_bound._str_upper_bound()}.") + + class _Bound(ABC): + def __init__(self, value: float): + self._value = value + + def __str__(self) -> str: + return str(self._value) + + @abstractmethod + def _str_lower_bound(self) -> str: + pass + + @abstractmethod + def _str_upper_bound(self) -> str: + pass + + class _ClosedBound(_Bound): + def __init__(self, value: float): + super().__init__(value) + + def _str_lower_bound(self) -> str: + return f"[{self._value}" + + def _str_upper_bound(self) -> str: + return f"{self._value}]" + + class _OpenBound(_Bound): + def __init__(self, value: float): + super().__init__(value) + + def _str_lower_bracket(self) -> str: + return f"({self._value}" + + def _str_upper_bracket(self) -> str: + return f"{self._value})" + + class _Infinity(_OpenBound): + + def __init__(self): + super().__init__(float("nan")) + + def __str__(self) -> str: + return "\u221e" From 9a1ecf4f42f377b44789269792cb4521f67fb041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:55:48 +0200 Subject: [PATCH 02/77] Fix formatting --- src/safeds/exceptions/_generic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index d35cd2bd3..4d63671b9 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -42,20 +42,20 @@ def __init__(self, value: float): super().__init__(value) def _str_lower_bound(self) -> str: - return f"[{self._value}" + return f"[{self}" def _str_upper_bound(self) -> str: - return f"{self._value}]" + return f"{self}]" class _OpenBound(_Bound): def __init__(self, value: float): super().__init__(value) def _str_lower_bracket(self) -> str: - return f"({self._value}" + return f"({self}" def _str_upper_bracket(self) -> str: - return f"{self._value})" + return f"{self})" class _Infinity(_OpenBound): From cbc80191291a880cd7ab61fedf604a114a16278e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:56:53 +0200 Subject: [PATCH 03/77] Fix typos --- src/safeds/exceptions/_generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 4d63671b9..b726b7b3c 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -51,10 +51,10 @@ class _OpenBound(_Bound): def __init__(self, value: float): super().__init__(value) - def _str_lower_bracket(self) -> str: + def _str_lower_bound(self) -> str: return f"({self}" - def _str_upper_bracket(self) -> str: + def _str_upper_bound(self) -> str: return f"{self})" class _Infinity(_OpenBound): From 56aa48410f568ecf63564428988b0b767b6f29a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:49:36 +0200 Subject: [PATCH 04/77] Add test for OutOfBoundsError --- src/safeds/exceptions/__init__.py | 15 +++- src/safeds/exceptions/_generic.py | 88 ++++++++++++------- .../exceptions/test_out_of_bounds_error.py | 18 ++++ 3 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 tests/safeds/data/exceptions/test_out_of_bounds_error.py diff --git a/src/safeds/exceptions/__init__.py b/src/safeds/exceptions/__init__.py index 41baa46d5..5c949a3f5 100644 --- a/src/safeds/exceptions/__init__.py +++ b/src/safeds/exceptions/__init__.py @@ -13,7 +13,14 @@ ValueNotPresentWhenFittedError, WrongFileExtensionError, ) -from safeds.exceptions._generic import OutOfBoundsError +from safeds.exceptions._generic import ( + Bound, + ClosedBound, + Infinity, + MinInfinity, + OpenBound, + OutOfBoundsError, +) from safeds.exceptions._ml import ( DatasetContainsTargetError, DatasetMissesDataError, @@ -47,4 +54,10 @@ "PredictionError", "UntaggedTableError", "DatasetMissesDataError", + # Other + "Bound", + "ClosedBound", + "Infinity", + "MinInfinity", + "OpenBound", ] diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index b726b7b3c..f46d5fc82 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -17,50 +17,76 @@ class OutOfBoundsError(ValueError): The lower Bound. """ - def __init__(self, actual: float, *, lower_bound: _Bound | None = None, upper_bound: _Bound | None = None): + def __init__(self, actual: float, *, lower_bound: Bound = None, upper_bound: Bound = None): if lower_bound is None and upper_bound is None: raise NotImplementedError("Value cannot be out of bounds if there are no bounds.") + if lower_bound is None: + lower_bound = MinInfinity() + if upper_bound is None: + upper_bound = Infinity() super().__init__(f"{actual} is not inside {lower_bound._str_lower_bound()}, {upper_bound._str_upper_bound()}.") - class _Bound(ABC): - def __init__(self, value: float): - self._value = value - def __str__(self) -> str: - return str(self._value) +class Bound(ABC): + def __init__(self, value: float): + self._value = value - @abstractmethod - def _str_lower_bound(self) -> str: - pass + def __str__(self) -> str: + return str(self._value) - @abstractmethod - def _str_upper_bound(self) -> str: - pass + def _is_float(self) -> bool: + return True - class _ClosedBound(_Bound): - def __init__(self, value: float): - super().__init__(value) + @abstractmethod + def _str_lower_bound(self) -> str: + pass - def _str_lower_bound(self) -> str: - return f"[{self}" + @abstractmethod + def _str_upper_bound(self) -> str: + pass - def _str_upper_bound(self) -> str: - return f"{self}]" - class _OpenBound(_Bound): - def __init__(self, value: float): - super().__init__(value) +class ClosedBound(Bound): + def __init__(self, value: float): + super().__init__(value) - def _str_lower_bound(self) -> str: - return f"({self}" + def _str_lower_bound(self) -> str: + return f"[{self}" - def _str_upper_bound(self) -> str: - return f"{self})" + def _str_upper_bound(self) -> str: + return f"{self}]" - class _Infinity(_OpenBound): - def __init__(self): - super().__init__(float("nan")) +class OpenBound(Bound): + def __init__(self, value: float): + super().__init__(value) - def __str__(self) -> str: - return "\u221e" + def _str_lower_bound(self) -> str: + return f"({self}" + + def _str_upper_bound(self) -> str: + return f"{self})" + + +class Infinity(OpenBound): + + def __init__(self): + super().__init__(float("nan")) + + def __str__(self) -> str: + return "\u221e" + + def _is_float(self) -> bool: + return False + + +class MinInfinity(OpenBound): + + def __init__(self): + super().__init__(float("nan")) + + def __str__(self) -> str: + return "-\u221e" + + def _is_float(self) -> bool: + return False diff --git a/tests/safeds/data/exceptions/test_out_of_bounds_error.py b/tests/safeds/data/exceptions/test_out_of_bounds_error.py new file mode 100644 index 000000000..3e71cb0fe --- /dev/null +++ b/tests/safeds/data/exceptions/test_out_of_bounds_error.py @@ -0,0 +1,18 @@ +import pytest +from safeds.exceptions import OutOfBoundsError, Bound, ClosedBound + + +@pytest.mark.parametrize( + ("actual", "lower_bound", "upper_bound"), + [ + (42, ClosedBound(-1), ClosedBound(1)), + ], + ids=["closed_closed"] +) +def test_should_raise_out_of_bounds_error(actual: float, lower_bound: Bound, upper_bound: Bound) -> None: + #with pytest.raises(OutOfBoundsError): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + + +def test_should_raise_not_implemented_error() -> None: + pass From 63c8640a9649a3e751d3092fff5edc011699344f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gr=C3=A9us?= Date: Fri, 7 Jul 2023 15:51:11 +0200 Subject: [PATCH 05/77] moved test_out_of_bounds_error.py --- tests/safeds/exceptions/__init__.py | 0 tests/safeds/{data => }/exceptions/test_out_of_bounds_error.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/safeds/exceptions/__init__.py rename tests/safeds/{data => }/exceptions/test_out_of_bounds_error.py (100%) diff --git a/tests/safeds/exceptions/__init__.py b/tests/safeds/exceptions/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/safeds/data/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py similarity index 100% rename from tests/safeds/data/exceptions/test_out_of_bounds_error.py rename to tests/safeds/exceptions/test_out_of_bounds_error.py From 69ec60bb546800a54696b8455af655d502bec992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gr=C3=A9us?= Date: Fri, 7 Jul 2023 16:06:59 +0200 Subject: [PATCH 06/77] test: Added tests for `OutOfBoundsError` --- src/safeds/exceptions/_generic.py | 2 +- .../exceptions/test_out_of_bounds_error.py | 35 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index f46d5fc82..2123cdc5c 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -18,7 +18,7 @@ class OutOfBoundsError(ValueError): """ def __init__(self, actual: float, *, lower_bound: Bound = None, upper_bound: Bound = None): - if lower_bound is None and upper_bound is None: + if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and (upper_bound is None or isinstance(upper_bound, Infinity)): raise NotImplementedError("Value cannot be out of bounds if there are no bounds.") if lower_bound is None: lower_bound = MinInfinity() diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 3e71cb0fe..3cdb3ea9d 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -1,18 +1,31 @@ +import re + import pytest -from safeds.exceptions import OutOfBoundsError, Bound, ClosedBound +from safeds.exceptions import OutOfBoundsError, Bound, ClosedBound, OpenBound, MinInfinity, Infinity @pytest.mark.parametrize( - ("actual", "lower_bound", "upper_bound"), + ("actual", "lower_bound", "match_lower"), [ - (42, ClosedBound(-1), ClosedBound(1)), + (42, ClosedBound(-1), "[-1"), + (42, OpenBound(-1), "(-1"), + (42, MinInfinity(), "(-\u221e"), ], - ids=["closed_closed"] + ids=["lb_closed_-1", "lb_open_-1", "lb_open_inf"] ) -def test_should_raise_out_of_bounds_error(actual: float, lower_bound: Bound, upper_bound: Bound) -> None: - #with pytest.raises(OutOfBoundsError): - raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - - -def test_should_raise_not_implemented_error() -> None: - pass +@pytest.mark.parametrize( + ("upper_bound", "match_upper"), + [ + (ClosedBound(1), "1]"), + (OpenBound(1), "1)"), + (Infinity(), "\u221e)"), + ], + ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf"] +) +def test_should_raise_in_out_of_bounds_error(actual: float, lower_bound: Bound | None, upper_bound: Bound | None, match_lower: str, match_upper: str) -> None: + if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and (upper_bound is None or isinstance(upper_bound, Infinity)): + with pytest.raises(NotImplementedError, match=r"Value cannot be out of bounds if there are no bounds."): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + else: + with pytest.raises(OutOfBoundsError, match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}."): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) From 3a40c6a437cfbe2782dd8fc4909af5abba979063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Fri, 7 Jul 2023 16:11:40 +0200 Subject: [PATCH 07/77] Fix type hints --- src/safeds/exceptions/_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 2123cdc5c..a4a469a17 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -17,7 +17,7 @@ class OutOfBoundsError(ValueError): The lower Bound. """ - def __init__(self, actual: float, *, lower_bound: Bound = None, upper_bound: Bound = None): + def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and (upper_bound is None or isinstance(upper_bound, Infinity)): raise NotImplementedError("Value cannot be out of bounds if there are no bounds.") if lower_bound is None: From 0805bb8250a1ef2b49987cd3984797d330d1a0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gr=C3=A9us?= Date: Fri, 7 Jul 2023 16:13:30 +0200 Subject: [PATCH 08/77] feat: Added `NotImplementedError` if lower bound is greater than upper bound in `OutOfBoundsError` --- src/safeds/exceptions/_generic.py | 6 ++++-- tests/safeds/exceptions/test_out_of_bounds_error.py | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 2123cdc5c..ae681aced 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -24,6 +24,8 @@ def __init__(self, actual: float, *, lower_bound: Bound = None, upper_bound: Bou lower_bound = MinInfinity() if upper_bound is None: upper_bound = Infinity() + if upper_bound._value < lower_bound._value: + raise NotImplementedError("The upper bound cannot be less than the lower bound.") super().__init__(f"{actual} is not inside {lower_bound._str_lower_bound()}, {upper_bound._str_upper_bound()}.") @@ -71,7 +73,7 @@ def _str_upper_bound(self) -> str: class Infinity(OpenBound): def __init__(self): - super().__init__(float("nan")) + super().__init__(float("inf")) def __str__(self) -> str: return "\u221e" @@ -83,7 +85,7 @@ def _is_float(self) -> bool: class MinInfinity(OpenBound): def __init__(self): - super().__init__(float("nan")) + super().__init__(float("-inf")) def __str__(self) -> str: return "-\u221e" diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 3cdb3ea9d..48134973a 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -10,8 +10,9 @@ (42, ClosedBound(-1), "[-1"), (42, OpenBound(-1), "(-1"), (42, MinInfinity(), "(-\u221e"), + (42, None, "(-\u221e"), ], - ids=["lb_closed_-1", "lb_open_-1", "lb_open_inf"] + ids=["lb_closed_-1", "lb_open_-1", "lb_open_inf", "lb_none"] ) @pytest.mark.parametrize( ("upper_bound", "match_upper"), @@ -19,13 +20,17 @@ (ClosedBound(1), "1]"), (OpenBound(1), "1)"), (Infinity(), "\u221e)"), + (None, "\u221e)"), ], - ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf"] + ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf", "ub_none"] ) def test_should_raise_in_out_of_bounds_error(actual: float, lower_bound: Bound | None, upper_bound: Bound | None, match_lower: str, match_upper: str) -> None: if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and (upper_bound is None or isinstance(upper_bound, Infinity)): with pytest.raises(NotImplementedError, match=r"Value cannot be out of bounds if there are no bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + elif lower_bound is not None and upper_bound is not None and lower_bound._value > upper_bound._value: + with pytest.raises(NotImplementedError, match=r"The upper bound cannot be less than the lower bound."): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) else: with pytest.raises(OutOfBoundsError, match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) From 19d85699c3e66d67f460340ee378d94015e41384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Fri, 7 Jul 2023 16:20:55 +0200 Subject: [PATCH 09/77] Add None return type in __init__ methods --- src/safeds/exceptions/_generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 70d08eadf..41e3ed73e 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -72,7 +72,7 @@ def _str_upper_bound(self) -> str: class Infinity(OpenBound): - def __init__(self): + def __init__(self) -> None: super().__init__(float("inf")) def __str__(self) -> str: @@ -84,7 +84,7 @@ def _is_float(self) -> bool: class MinInfinity(OpenBound): - def __init__(self): + def __init__(self) -> None: super().__init__(float("-inf")) def __str__(self) -> str: From 4575b5835ae9e5c7d7bb604195eae4ffb9e72df7 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:22:50 +0000 Subject: [PATCH 10/77] style: apply automated linter fixes --- src/safeds/exceptions/_generic.py | 6 +++--- .../exceptions/test_out_of_bounds_error.py | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 41e3ed73e..da0a05ff5 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -18,7 +18,9 @@ class OutOfBoundsError(ValueError): """ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): - if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and (upper_bound is None or isinstance(upper_bound, Infinity)): + if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( + upper_bound is None or isinstance(upper_bound, Infinity) + ): raise NotImplementedError("Value cannot be out of bounds if there are no bounds.") if lower_bound is None: lower_bound = MinInfinity() @@ -71,7 +73,6 @@ def _str_upper_bound(self) -> str: class Infinity(OpenBound): - def __init__(self) -> None: super().__init__(float("inf")) @@ -83,7 +84,6 @@ def _is_float(self) -> bool: class MinInfinity(OpenBound): - def __init__(self) -> None: super().__init__(float("-inf")) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 48134973a..3a4e4b3c0 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -1,7 +1,7 @@ import re import pytest -from safeds.exceptions import OutOfBoundsError, Bound, ClosedBound, OpenBound, MinInfinity, Infinity +from safeds.exceptions import Bound, ClosedBound, Infinity, MinInfinity, OpenBound, OutOfBoundsError @pytest.mark.parametrize( @@ -12,7 +12,7 @@ (42, MinInfinity(), "(-\u221e"), (42, None, "(-\u221e"), ], - ids=["lb_closed_-1", "lb_open_-1", "lb_open_inf", "lb_none"] + ids=["lb_closed_-1", "lb_open_-1", "lb_open_inf", "lb_none"], ) @pytest.mark.parametrize( ("upper_bound", "match_upper"), @@ -22,15 +22,21 @@ (Infinity(), "\u221e)"), (None, "\u221e)"), ], - ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf", "ub_none"] + ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf", "ub_none"], ) -def test_should_raise_in_out_of_bounds_error(actual: float, lower_bound: Bound | None, upper_bound: Bound | None, match_lower: str, match_upper: str) -> None: - if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and (upper_bound is None or isinstance(upper_bound, Infinity)): +def test_should_raise_in_out_of_bounds_error( + actual: float, lower_bound: Bound | None, upper_bound: Bound | None, match_lower: str, match_upper: str, +) -> None: + if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( + upper_bound is None or isinstance(upper_bound, Infinity) + ): with pytest.raises(NotImplementedError, match=r"Value cannot be out of bounds if there are no bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) elif lower_bound is not None and upper_bound is not None and lower_bound._value > upper_bound._value: with pytest.raises(NotImplementedError, match=r"The upper bound cannot be less than the lower bound."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) else: - with pytest.raises(OutOfBoundsError, match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}."): + with pytest.raises( + OutOfBoundsError, match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}.", + ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) From d23718b7bffcae8ba20a5f3759cd6fda621f6cc7 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:24:50 +0000 Subject: [PATCH 11/77] style: apply automated linter fixes --- tests/safeds/exceptions/test_out_of_bounds_error.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 3a4e4b3c0..f0cb366c6 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -25,7 +25,11 @@ ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf", "ub_none"], ) def test_should_raise_in_out_of_bounds_error( - actual: float, lower_bound: Bound | None, upper_bound: Bound | None, match_lower: str, match_upper: str, + actual: float, + lower_bound: Bound | None, + upper_bound: Bound | None, + match_lower: str, + match_upper: str, ) -> None: if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( upper_bound is None or isinstance(upper_bound, Infinity) @@ -37,6 +41,7 @@ def test_should_raise_in_out_of_bounds_error( raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) else: with pytest.raises( - OutOfBoundsError, match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}.", + OutOfBoundsError, + match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) From d529a4acfdeab56e1c9235d835c2eba5e4cc00d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gr=C3=A9us?= Date: Fri, 7 Jul 2023 16:41:11 +0200 Subject: [PATCH 12/77] feat: Added check for compare to lower and upper bounds --- src/safeds/exceptions/_generic.py | 24 +++++++ .../exceptions/test_out_of_bounds_error.py | 62 +++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 70d08eadf..fa62dd069 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -26,6 +26,10 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou upper_bound = Infinity() if upper_bound._value < lower_bound._value: raise NotImplementedError("The upper bound cannot be less than the lower bound.") + if not lower_bound._cmp_lower_bound(actual): + raise NotImplementedError("The value should not be lower than the interval.") + if not upper_bound._cmp_upper_bound(actual): + raise NotImplementedError("The value should not be larger than the interval.") super().__init__(f"{actual} is not inside {lower_bound._str_lower_bound()}, {upper_bound._str_upper_bound()}.") @@ -47,6 +51,14 @@ def _str_lower_bound(self) -> str: def _str_upper_bound(self) -> str: pass + @abstractmethod + def _cmp_lower_bound(self, cmp_to: float) -> bool: + pass + + @abstractmethod + def _cmp_upper_bound(self, cmp_to: float) -> bool: + pass + class ClosedBound(Bound): def __init__(self, value: float): @@ -58,6 +70,12 @@ def _str_lower_bound(self) -> str: def _str_upper_bound(self) -> str: return f"{self}]" + def _cmp_lower_bound(self, cmp_to: float) -> bool: + return cmp_to > self._value + + def _cmp_upper_bound(self, cmp_to: float) -> bool: + return cmp_to < self._value + class OpenBound(Bound): def __init__(self, value: float): @@ -69,6 +87,12 @@ def _str_lower_bound(self) -> str: def _str_upper_bound(self) -> str: return f"{self})" + def _cmp_lower_bound(self, cmp_to: float) -> bool: + return cmp_to >= self._value + + def _cmp_upper_bound(self, cmp_to: float) -> bool: + return cmp_to <= self._value + class Infinity(OpenBound): diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 48134973a..25c468ccd 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -5,12 +5,19 @@ @pytest.mark.parametrize( - ("actual", "lower_bound", "match_lower"), + "actual", [ - (42, ClosedBound(-1), "[-1"), - (42, OpenBound(-1), "(-1"), - (42, MinInfinity(), "(-\u221e"), - (42, None, "(-\u221e"), + 0, 1, -1, 2, -2, float("inf"), float("-inf") + ], + ids=["0", "1", "-1", "2", "-2", "inf", "-inf"] +) +@pytest.mark.parametrize( + ("lower_bound", "match_lower"), + [ + (ClosedBound(-1), "[-1"), + (OpenBound(-1), "(-1"), + (MinInfinity(), "(-\u221e"), + (None, "(-\u221e"), ], ids=["lb_closed_-1", "lb_open_-1", "lb_open_inf", "lb_none"] ) @@ -28,9 +35,52 @@ def test_should_raise_in_out_of_bounds_error(actual: float, lower_bound: Bound | if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and (upper_bound is None or isinstance(upper_bound, Infinity)): with pytest.raises(NotImplementedError, match=r"Value cannot be out of bounds if there are no bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - elif lower_bound is not None and upper_bound is not None and lower_bound._value > upper_bound._value: + elif lower_bound is not None and upper_bound is not None and upper_bound._value < lower_bound._value: with pytest.raises(NotImplementedError, match=r"The upper bound cannot be less than the lower bound."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + elif lower_bound is not None and not lower_bound._cmp_lower_bound(actual): + with pytest.raises(NotImplementedError, match=r"The value should not be lower than the interval."): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + elif upper_bound is not None and not upper_bound._cmp_upper_bound(actual): + with pytest.raises(NotImplementedError, match=r"The value should not be larger than the interval."): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) else: with pytest.raises(OutOfBoundsError, match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + +@pytest.mark.parametrize( + ("value", "expected_value", "bound", "lower_bound"), + [ + (2, False, ClosedBound(2), False), + (2, False, ClosedBound(2), True), + (2, True, ClosedBound(3), False), + (2, True, ClosedBound(1), True), + (2, True, OpenBound(2), False), + (2, True, OpenBound(2), True), + (2, False, OpenBound(1), False), + (2, False, OpenBound(3), True), + (2, False, Infinity(), True), + (2, True, Infinity(), False), + (2, True, MinInfinity(), True), + (2, False, MinInfinity(), False), + ], + ids=[ + "ex_false-close_2-upper", + "ex_false-close_2-lower", + "ex_true-close_3-upper", + "ex_true-close_1-lower", + "ex_true-open_2-upper", + "ex_true-open_2-lower", + "ex_false-open_1-upper", + "ex_false-open_3-lower", + "ex_false-inf-lower", + "ex_true-inf-upper", + "ex_true--inf-lower", + "ex_false--inf-upper", + ] +) +def test_should_return_true_if_value_in_bounds(value: float, expected_value: bool, bound: Bound, lower_bound: bool) -> None: + if lower_bound: + assert expected_value == bound._cmp_lower_bound(value) + else: + assert expected_value == bound._cmp_upper_bound(value) From f08997552ba8f689acf892374df8a667c72d36ab Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:43:41 +0000 Subject: [PATCH 13/77] style: apply automated linter fixes --- tests/safeds/exceptions/test_out_of_bounds_error.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 517d43483..dd5e664ea 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -5,11 +5,7 @@ @pytest.mark.parametrize( - "actual", - [ - 0, 1, -1, 2, -2, float("inf"), float("-inf") - ], - ids=["0", "1", "-1", "2", "-2", "inf", "-inf"] + "actual", [0, 1, -1, 2, -2, float("inf"), float("-inf")], ids=["0", "1", "-1", "2", "-2", "inf", "-inf"], ) @pytest.mark.parametrize( ("lower_bound", "match_lower"), @@ -59,6 +55,7 @@ def test_should_raise_in_out_of_bounds_error( ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + @pytest.mark.parametrize( ("value", "expected_value", "bound", "lower_bound"), [ @@ -88,9 +85,11 @@ def test_should_raise_in_out_of_bounds_error( "ex_true-inf-upper", "ex_true--inf-lower", "ex_false--inf-upper", - ] + ], ) -def test_should_return_true_if_value_in_bounds(value: float, expected_value: bool, bound: Bound, lower_bound: bool) -> None: +def test_should_return_true_if_value_in_bounds( + value: float, expected_value: bool, bound: Bound, lower_bound: bool, +) -> None: if lower_bound: assert expected_value == bound._cmp_lower_bound(value) else: From 83f795243ccdbe9d86a3b81c787ee2b1ed7e5629 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 7 Jul 2023 14:45:27 +0000 Subject: [PATCH 14/77] style: apply automated linter fixes --- tests/safeds/exceptions/test_out_of_bounds_error.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index dd5e664ea..519547839 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -5,7 +5,9 @@ @pytest.mark.parametrize( - "actual", [0, 1, -1, 2, -2, float("inf"), float("-inf")], ids=["0", "1", "-1", "2", "-2", "inf", "-inf"], + "actual", + [0, 1, -1, 2, -2, float("inf"), float("-inf")], + ids=["0", "1", "-1", "2", "-2", "inf", "-inf"], ) @pytest.mark.parametrize( ("lower_bound", "match_lower"), @@ -88,7 +90,10 @@ def test_should_raise_in_out_of_bounds_error( ], ) def test_should_return_true_if_value_in_bounds( - value: float, expected_value: bool, bound: Bound, lower_bound: bool, + value: float, + expected_value: bool, + bound: Bound, + lower_bound: bool, ) -> None: if lower_bound: assert expected_value == bound._cmp_lower_bound(value) From 6dc82b15039519b1b4237cdfaf6ed9a058227461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 16:38:35 +0200 Subject: [PATCH 15/77] Add docstrings to public methods in _generic.py --- src/safeds/exceptions/_generic.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 5c48222f9..a6beca218 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -5,16 +5,16 @@ class OutOfBoundsError(ValueError): """ - A generic exception to signal that a (float) value is out of bounds. + A generic exception that can be used to signal that a (float) value is outside its expected range. Parameters ---------- actual: float The actual value. - lower_bound: _Bound | None - The lower Bound. - upper_bound: _Bound | None + lower_bound: Bound | None The lower Bound. + upper_bound: Bound | None + The upper Bound. """ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): @@ -36,10 +36,13 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou class Bound(ABC): + """Abstract base class for (lower or upper) Bounds on a float value.""" + def __init__(self, value: float): self._value = value def __str__(self) -> str: + """Get a string representation of the concrete value of the Bound.""" return str(self._value) def _is_float(self) -> bool: @@ -63,6 +66,8 @@ def _cmp_upper_bound(self, cmp_to: float) -> bool: class ClosedBound(Bound): + """A closed Bound, i.e. the value on the border belongs to the range.""" + def __init__(self, value: float): super().__init__(value) @@ -80,6 +85,12 @@ def _cmp_upper_bound(self, cmp_to: float) -> bool: class OpenBound(Bound): + """ + An open Bound, i.e. the value on the border does not belong to the range. + + May be infinite (unbounded). + """ + def __init__(self, value: float): super().__init__(value) @@ -97,10 +108,13 @@ def _cmp_upper_bound(self, cmp_to: float) -> bool: class Infinity(OpenBound): + """An infinite or unrestricted upper Bound.""" + def __init__(self) -> None: super().__init__(float("inf")) def __str__(self) -> str: + """Get a string representation of the concrete value of the Bound.""" return "\u221e" def _is_float(self) -> bool: @@ -108,10 +122,13 @@ def _is_float(self) -> bool: class MinInfinity(OpenBound): + """An infinite or unrestricted lower Bound.""" + def __init__(self) -> None: super().__init__(float("-inf")) def __str__(self) -> str: + """Get a string representation of the concrete value of the Bound.""" return "-\u221e" def _is_float(self) -> bool: From 97fdca4ea2ea7f050c9943c2bb4eddaa0aed4f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 16:41:01 +0200 Subject: [PATCH 16/77] Remove unused _is_float method from Bound classes --- src/safeds/exceptions/_generic.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index a6beca218..7deb50b1b 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -45,9 +45,6 @@ def __str__(self) -> str: """Get a string representation of the concrete value of the Bound.""" return str(self._value) - def _is_float(self) -> bool: - return True - @abstractmethod def _str_lower_bound(self) -> str: pass @@ -117,9 +114,6 @@ def __str__(self) -> str: """Get a string representation of the concrete value of the Bound.""" return "\u221e" - def _is_float(self) -> bool: - return False - class MinInfinity(OpenBound): """An infinite or unrestricted lower Bound.""" @@ -130,6 +124,3 @@ def __init__(self) -> None: def __str__(self) -> str: """Get a string representation of the concrete value of the Bound.""" return "-\u221e" - - def _is_float(self) -> bool: - return False From 1d49617d61a4828db1ba793eece756f70c661913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 16:49:30 +0200 Subject: [PATCH 17/77] Fix typo in OutOfBoundsError test --- tests/safeds/exceptions/test_out_of_bounds_error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 519547839..d2e052cc4 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -29,7 +29,7 @@ ], ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf", "ub_none"], ) -def test_should_raise_in_out_of_bounds_error( +def test_should_raise_out_of_bounds_error( actual: float, lower_bound: Bound | None, upper_bound: Bound | None, From 9f3ef110c48ae2bd4eb81fc13d14d152bc6880ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 16:58:30 +0200 Subject: [PATCH 18/77] Make str functions public Also added docstrings accordingly. --- src/safeds/exceptions/_generic.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 7deb50b1b..44a0eac00 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -32,7 +32,7 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou raise NotImplementedError("The value should not be lower than the interval.") if not upper_bound._cmp_upper_bound(actual): raise NotImplementedError("The value should not be larger than the interval.") - super().__init__(f"{actual} is not inside {lower_bound._str_lower_bound()}, {upper_bound._str_upper_bound()}.") + super().__init__(f"{actual} is not inside {lower_bound.str_lower_bound()}, {upper_bound.str_upper_bound()}.") class Bound(ABC): @@ -46,12 +46,12 @@ def __str__(self) -> str: return str(self._value) @abstractmethod - def _str_lower_bound(self) -> str: - pass + def str_lower_bound(self) -> str: + """Get a string representation of the Bound as the lower Bound of an interval.""" @abstractmethod - def _str_upper_bound(self) -> str: - pass + def str_upper_bound(self) -> str: + """Get a string representation of the Bound as the upper Bound of an interval.""" @abstractmethod def _cmp_lower_bound(self, cmp_to: float) -> bool: @@ -68,10 +68,12 @@ class ClosedBound(Bound): def __init__(self, value: float): super().__init__(value) - def _str_lower_bound(self) -> str: + def str_lower_bound(self) -> str: + """Get a string representation of the Bound as the lower Bound of an interval.""" return f"[{self}" - def _str_upper_bound(self) -> str: + def str_upper_bound(self) -> str: + """Get a string representation of the Bound as the upper Bound of an interval.""" return f"{self}]" def _cmp_lower_bound(self, cmp_to: float) -> bool: @@ -91,10 +93,12 @@ class OpenBound(Bound): def __init__(self, value: float): super().__init__(value) - def _str_lower_bound(self) -> str: + def str_lower_bound(self) -> str: + """Get a string representation of the Bound as the lower Bound of an interval.""" return f"({self}" - def _str_upper_bound(self) -> str: + def str_upper_bound(self) -> str: + """Get a string representation of the Bound as the upper Bound of an interval.""" return f"{self})" def _cmp_lower_bound(self, cmp_to: float) -> bool: From 0f0132d8d074e4eba70d73dc9e6d8b59e0007aa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:12:16 +0200 Subject: [PATCH 19/77] Rename compare functions. Also made public and added docstrings. --- src/safeds/exceptions/_generic.py | 32 +++++++++++-------- .../exceptions/test_out_of_bounds_error.py | 8 ++--- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 44a0eac00..ad7872e56 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -28,9 +28,9 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou upper_bound = Infinity() if upper_bound._value < lower_bound._value: raise NotImplementedError("The upper bound cannot be less than the lower bound.") - if not lower_bound._cmp_lower_bound(actual): + if not lower_bound.check_lower_bound(actual): raise NotImplementedError("The value should not be lower than the interval.") - if not upper_bound._cmp_upper_bound(actual): + if not upper_bound.check_upper_bound(actual): raise NotImplementedError("The value should not be larger than the interval.") super().__init__(f"{actual} is not inside {lower_bound.str_lower_bound()}, {upper_bound.str_upper_bound()}.") @@ -54,12 +54,12 @@ def str_upper_bound(self) -> str: """Get a string representation of the Bound as the upper Bound of an interval.""" @abstractmethod - def _cmp_lower_bound(self, cmp_to: float) -> bool: - pass + def check_lower_bound(self, value: float) -> bool: + """Check that a value does not exceed the Bound on the lower side.""" @abstractmethod - def _cmp_upper_bound(self, cmp_to: float) -> bool: - pass + def check_upper_bound(self, value: float) -> bool: + """Check that a value does not exceed the Bound on the upper side.""" class ClosedBound(Bound): @@ -76,11 +76,13 @@ def str_upper_bound(self) -> str: """Get a string representation of the Bound as the upper Bound of an interval.""" return f"{self}]" - def _cmp_lower_bound(self, cmp_to: float) -> bool: - return cmp_to > self._value + def check_lower_bound(self, value: float) -> bool: + """Check that a value does not exceed the Bound on the lower side.""" + return value > self._value - def _cmp_upper_bound(self, cmp_to: float) -> bool: - return cmp_to < self._value + def check_upper_bound(self, value: float) -> bool: + """Check that a value does not exceed the Bound on the upper side.""" + return value < self._value class OpenBound(Bound): @@ -101,11 +103,13 @@ def str_upper_bound(self) -> str: """Get a string representation of the Bound as the upper Bound of an interval.""" return f"{self})" - def _cmp_lower_bound(self, cmp_to: float) -> bool: - return cmp_to >= self._value + def check_lower_bound(self, value: float) -> bool: + """Check that a value does not exceed the Bound on the lower side.""" + return value >= self._value - def _cmp_upper_bound(self, cmp_to: float) -> bool: - return cmp_to <= self._value + def check_upper_bound(self, value: float) -> bool: + """Check that a value does not exceed the Bound on the upper side.""" + return value <= self._value class Infinity(OpenBound): diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index d2e052cc4..cf517514d 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -44,10 +44,10 @@ def test_should_raise_out_of_bounds_error( elif lower_bound is not None and upper_bound is not None and upper_bound._value < lower_bound._value: with pytest.raises(NotImplementedError, match=r"The upper bound cannot be less than the lower bound."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - elif lower_bound is not None and not lower_bound._cmp_lower_bound(actual): + elif lower_bound is not None and not lower_bound.check_lower_bound(actual): with pytest.raises(NotImplementedError, match=r"The value should not be lower than the interval."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - elif upper_bound is not None and not upper_bound._cmp_upper_bound(actual): + elif upper_bound is not None and not upper_bound.check_upper_bound(actual): with pytest.raises(NotImplementedError, match=r"The value should not be larger than the interval."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) else: @@ -96,6 +96,6 @@ def test_should_return_true_if_value_in_bounds( lower_bound: bool, ) -> None: if lower_bound: - assert expected_value == bound._cmp_lower_bound(value) + assert expected_value == bound.check_lower_bound(value) else: - assert expected_value == bound._cmp_upper_bound(value) + assert expected_value == bound.check_upper_bound(value) From 97096c4105850ecae931d40078ba22a725d5cc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:53:00 +0200 Subject: [PATCH 20/77] Fix bounds checking logic --- src/safeds/exceptions/_generic.py | 40 +++++++++++++------ .../exceptions/test_out_of_bounds_error.py | 8 ++-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index ad7872e56..47bef07c0 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -69,20 +69,20 @@ def __init__(self, value: float): super().__init__(value) def str_lower_bound(self) -> str: - """Get a string representation of the Bound as the lower Bound of an interval.""" + """Get a string representation of the ClosedBound as the lower Bound of an interval.""" return f"[{self}" def str_upper_bound(self) -> str: - """Get a string representation of the Bound as the upper Bound of an interval.""" + """Get a string representation of the ClosedBound as the upper Bound of an interval.""" return f"{self}]" def check_lower_bound(self, value: float) -> bool: - """Check that a value does not exceed the Bound on the lower side.""" - return value > self._value + """Check that a value is not strictly lower than the ClosedBound.""" + return value >= self._value def check_upper_bound(self, value: float) -> bool: - """Check that a value does not exceed the Bound on the upper side.""" - return value < self._value + """Check that a value is not strictly higher than the ClosedBound.""" + return value <= self._value class OpenBound(Bound): @@ -96,20 +96,20 @@ def __init__(self, value: float): super().__init__(value) def str_lower_bound(self) -> str: - """Get a string representation of the Bound as the lower Bound of an interval.""" + """Get a string representation of the OpenBound as the lower Bound of an interval.""" return f"({self}" def str_upper_bound(self) -> str: - """Get a string representation of the Bound as the upper Bound of an interval.""" + """Get a string representation of the OpenBound as the upper Bound of an interval.""" return f"{self})" def check_lower_bound(self, value: float) -> bool: - """Check that a value does not exceed the Bound on the lower side.""" - return value >= self._value + """Check that a value is not lower or equal to the Bound.""" + return value > self._value def check_upper_bound(self, value: float) -> bool: - """Check that a value does not exceed the Bound on the upper side.""" - return value <= self._value + """Check that a value is not higher ot equal to the Bound.""" + return value < self._value class Infinity(OpenBound): @@ -122,6 +122,14 @@ def __str__(self) -> str: """Get a string representation of the concrete value of the Bound.""" return "\u221e" + def check_lower_bound(self, _: float) -> bool: + """Check that a value does not exceed the unrestricted Bound on the lower side. Always False.""" + return False + + def check_upper_bound(self, _: float) -> bool: + """Check that a value does not exceed the unrestricted Bound on the upper side. Always true.""" + return True + class MinInfinity(OpenBound): """An infinite or unrestricted lower Bound.""" @@ -132,3 +140,11 @@ def __init__(self) -> None: def __str__(self) -> str: """Get a string representation of the concrete value of the Bound.""" return "-\u221e" + + def check_lower_bound(self, _: float) -> bool: + """Check that a value does not exceed the unrestricted Bound on the lower side. Always true.""" + return True + + def check_upper_bound(self, _: float) -> bool: + """Check that a value does not exceed the unrestricted Bound on the upper side. Always false.""" + return False diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index cf517514d..39e64f457 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -61,12 +61,12 @@ def test_should_raise_out_of_bounds_error( @pytest.mark.parametrize( ("value", "expected_value", "bound", "lower_bound"), [ - (2, False, ClosedBound(2), False), - (2, False, ClosedBound(2), True), + (2, True, ClosedBound(2), False), + (2, True, ClosedBound(2), True), (2, True, ClosedBound(3), False), (2, True, ClosedBound(1), True), - (2, True, OpenBound(2), False), - (2, True, OpenBound(2), True), + (2, False, OpenBound(2), False), + (2, False, OpenBound(2), True), (2, False, OpenBound(1), False), (2, False, OpenBound(3), True), (2, False, Infinity(), True), From 1a09b5fa1340414d883ab61bc70ef5aa0189b568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 18:19:39 +0200 Subject: [PATCH 21/77] Add missing testcase --- tests/safeds/exceptions/test_out_of_bounds_error.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 39e64f457..acd7d1614 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -41,9 +41,9 @@ def test_should_raise_out_of_bounds_error( ): with pytest.raises(NotImplementedError, match=r"Value cannot be out of bounds if there are no bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - elif lower_bound is not None and upper_bound is not None and upper_bound._value < lower_bound._value: + elif lower_bound is not None and upper_bound is not None: with pytest.raises(NotImplementedError, match=r"The upper bound cannot be less than the lower bound."): - raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) elif lower_bound is not None and not lower_bound.check_lower_bound(actual): with pytest.raises(NotImplementedError, match=r"The value should not be lower than the interval."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) From 2ce23809328daaef55d6ce2c60aa3fb3a29adf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 18:55:14 +0200 Subject: [PATCH 22/77] Improve exception messages and fix testcases --- src/safeds/exceptions/_generic.py | 11 +++---- .../exceptions/test_out_of_bounds_error.py | 33 +++++++++++-------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 47bef07c0..46035150a 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -21,17 +21,16 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( upper_bound is None or isinstance(upper_bound, Infinity) ): - raise NotImplementedError("Value cannot be out of bounds if there are no bounds.") + raise NotImplementedError("Illegal interval: Value cannot be out of bounds if there are no bounds.") if lower_bound is None: lower_bound = MinInfinity() if upper_bound is None: upper_bound = Infinity() if upper_bound._value < lower_bound._value: - raise NotImplementedError("The upper bound cannot be less than the lower bound.") - if not lower_bound.check_lower_bound(actual): - raise NotImplementedError("The value should not be lower than the interval.") - if not upper_bound.check_upper_bound(actual): - raise NotImplementedError("The value should not be larger than the interval.") + raise NotImplementedError("Illegal interval: The upper bound cannot be less than the lower bound.") + elif lower_bound.check_lower_bound(actual) and upper_bound.check_upper_bound(actual): + raise NotImplementedError("Illegal interval: Attempting to raise OutOfBoundsError, but value is not out " + "of bounds.") super().__init__(f"{actual} is not inside {lower_bound.str_lower_bound()}, {upper_bound.str_upper_bound()}.") diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index acd7d1614..0bf5c8dab 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -36,26 +36,31 @@ def test_should_raise_out_of_bounds_error( match_lower: str, match_upper: str, ) -> None: + # Check (-inf, inf) interval: if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( upper_bound is None or isinstance(upper_bound, Infinity) ): - with pytest.raises(NotImplementedError, match=r"Value cannot be out of bounds if there are no bounds."): + with pytest.raises(NotImplementedError, match=r"Illegal interval: Value cannot be out of bounds if there are " + r"no bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - elif lower_bound is not None and upper_bound is not None: - with pytest.raises(NotImplementedError, match=r"The upper bound cannot be less than the lower bound."): + return + # All tests: Check interval where lower > upper: + if lower_bound is not None and upper_bound is not None: + with pytest.raises(NotImplementedError, match=r"Illegal interval: The upper bound cannot be less than the " + r"lower bound."): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) - elif lower_bound is not None and not lower_bound.check_lower_bound(actual): - with pytest.raises(NotImplementedError, match=r"The value should not be lower than the interval."): - raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - elif upper_bound is not None and not upper_bound.check_upper_bound(actual): - with pytest.raises(NotImplementedError, match=r"The value should not be larger than the interval."): - raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) - else: - with pytest.raises( - OutOfBoundsError, - match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}.", - ): + # Check case where actual value lies inside the interval: + if (lower_bound is None or lower_bound.check_lower_bound(actual)) and (upper_bound is None or upper_bound.check_upper_bound(actual)): + with pytest.raises(NotImplementedError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, " + r"but value is not out of bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + return + # Check that error is raised correctly: + with pytest.raises( + OutOfBoundsError, + match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}.", + ): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) @pytest.mark.parametrize( From 7a96093b248fe3d6f3e36996087fb0ec78e4ce2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 19:02:05 +0200 Subject: [PATCH 23/77] Let the linter do the work --- tests/safeds/exceptions/test_out_of_bounds_error.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 0bf5c8dab..05a90e76b 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -40,19 +40,16 @@ def test_should_raise_out_of_bounds_error( if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( upper_bound is None or isinstance(upper_bound, Infinity) ): - with pytest.raises(NotImplementedError, match=r"Illegal interval: Value cannot be out of bounds if there are " - r"no bounds."): + with pytest.raises(NotImplementedError, match=r"Illegal interval: Value cannot be out of bounds if there are no bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # All tests: Check interval where lower > upper: if lower_bound is not None and upper_bound is not None: - with pytest.raises(NotImplementedError, match=r"Illegal interval: The upper bound cannot be less than the " - r"lower bound."): + with pytest.raises(NotImplementedError, match=r"Illegal interval: The upper bound cannot be less than the lower bound."): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: if (lower_bound is None or lower_bound.check_lower_bound(actual)) and (upper_bound is None or upper_bound.check_upper_bound(actual)): - with pytest.raises(NotImplementedError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, " - r"but value is not out of bounds."): + with pytest.raises(NotImplementedError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds."): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # Check that error is raised correctly: From c4d4fdc69a56ee915b5f27b8ccacf84e8b34cb1d Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:03:54 +0000 Subject: [PATCH 24/77] style: apply automated linter fixes --- src/safeds/exceptions/_generic.py | 5 +++-- .../exceptions/test_out_of_bounds_error.py | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 46035150a..34ed71381 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -29,8 +29,9 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou if upper_bound._value < lower_bound._value: raise NotImplementedError("Illegal interval: The upper bound cannot be less than the lower bound.") elif lower_bound.check_lower_bound(actual) and upper_bound.check_upper_bound(actual): - raise NotImplementedError("Illegal interval: Attempting to raise OutOfBoundsError, but value is not out " - "of bounds.") + raise NotImplementedError( + "Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds.", + ) super().__init__(f"{actual} is not inside {lower_bound.str_lower_bound()}, {upper_bound.str_upper_bound()}.") diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 05a90e76b..58a41064d 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -40,16 +40,25 @@ def test_should_raise_out_of_bounds_error( if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( upper_bound is None or isinstance(upper_bound, Infinity) ): - with pytest.raises(NotImplementedError, match=r"Illegal interval: Value cannot be out of bounds if there are no bounds."): + with pytest.raises( + NotImplementedError, match=r"Illegal interval: Value cannot be out of bounds if there are no bounds.", + ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # All tests: Check interval where lower > upper: if lower_bound is not None and upper_bound is not None: - with pytest.raises(NotImplementedError, match=r"Illegal interval: The upper bound cannot be less than the lower bound."): + with pytest.raises( + NotImplementedError, match=r"Illegal interval: The upper bound cannot be less than the lower bound.", + ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: - if (lower_bound is None or lower_bound.check_lower_bound(actual)) and (upper_bound is None or upper_bound.check_upper_bound(actual)): - with pytest.raises(NotImplementedError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds."): + if (lower_bound is None or lower_bound.check_lower_bound(actual)) and ( + upper_bound is None or upper_bound.check_upper_bound(actual) + ): + with pytest.raises( + NotImplementedError, + match=r"Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds.", + ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # Check that error is raised correctly: From 72e3eae880db762d02d02151b231b4c1ed754f84 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:05:56 +0000 Subject: [PATCH 25/77] style: apply automated linter fixes --- tests/safeds/exceptions/test_out_of_bounds_error.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 58a41064d..440c6da92 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -41,14 +41,16 @@ def test_should_raise_out_of_bounds_error( upper_bound is None or isinstance(upper_bound, Infinity) ): with pytest.raises( - NotImplementedError, match=r"Illegal interval: Value cannot be out of bounds if there are no bounds.", + NotImplementedError, + match=r"Illegal interval: Value cannot be out of bounds if there are no bounds.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # All tests: Check interval where lower > upper: if lower_bound is not None and upper_bound is not None: with pytest.raises( - NotImplementedError, match=r"Illegal interval: The upper bound cannot be less than the lower bound.", + NotImplementedError, + match=r"Illegal interval: The upper bound cannot be less than the lower bound.", ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: From 6b739485f977b3865003f40b8047f96e492facff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 19:18:40 +0200 Subject: [PATCH 26/77] Add docstring to __init__ method --- src/safeds/exceptions/_generic.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 34ed71381..47d887c76 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -18,6 +18,18 @@ class OutOfBoundsError(ValueError): """ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): + """ + Initialize an OutOfBoundError. + + Parameters + ---------- + actual: float + The actual value that is outside its expected range. + lower_bound: Bound | None + The lower bound of the expected range. + upper_bound: Bound | None + The upper bound of the expected range. + """ if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( upper_bound is None or isinstance(upper_bound, Infinity) ): From e7e1f9f5a9118da206bbe98dc16aafa51ea039a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sat, 8 Jul 2023 19:21:45 +0200 Subject: [PATCH 27/77] Remove parameters section in class docstring --- src/safeds/exceptions/_generic.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 47d887c76..a2e9db702 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -4,18 +4,7 @@ class OutOfBoundsError(ValueError): - """ - A generic exception that can be used to signal that a (float) value is outside its expected range. - - Parameters - ---------- - actual: float - The actual value. - lower_bound: Bound | None - The lower Bound. - upper_bound: Bound | None - The upper Bound. - """ + """A generic exception that can be used to signal that a (float) value is outside its expected range.""" def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): """ From 90079e05b6e638af34271444915e4ce7e4538d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 12:00:54 +0200 Subject: [PATCH 28/77] Use OutOfBoundsError in image tests --- src/safeds/data/image/containers/_image.py | 8 ++++---- tests/safeds/data/image/containers/test_image.py | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/safeds/data/image/containers/_image.py b/src/safeds/data/image/containers/_image.py index 7b12dd7c0..dc9d3da3a 100644 --- a/src/safeds/data/image/containers/_image.py +++ b/src/safeds/data/image/containers/_image.py @@ -10,8 +10,8 @@ from PIL import ImageEnhance, ImageFilter, ImageOps from PIL.Image import Image as PillowImage from PIL.Image import open as open_image - from safeds.data.image.typing import ImageFormat +from safeds.exceptions import ClosedBound, OutOfBoundsError class Image: @@ -293,7 +293,7 @@ def adjust_brightness(self, factor: float) -> Image: The Image with adjusted brightness. """ if factor < 0: - raise ValueError("Brightness factor has to be 0 or bigger") + raise OutOfBoundsError(factor, lower_bound=ClosedBound(0)) elif factor == 1: warnings.warn( "Brightness adjustment factor is 1.0, this will not make changes to the image.", @@ -322,7 +322,7 @@ def adjust_contrast(self, factor: float) -> Image: New image with adjusted contrast. """ if factor < 0: - raise ValueError("Contrast factor has to be 0 or bigger") + raise OutOfBoundsError(factor, lower_bound=ClosedBound(0)) elif factor == 1: warnings.warn( "Contrast adjustment factor is 1.0, this will not make changes to the image.", @@ -352,7 +352,7 @@ def adjust_color_balance(self, factor: float) -> Image: The new, adjusted image. """ if factor < 0: - raise ValueError("Color factor has to be 0 or bigger.") + raise OutOfBoundsError(factor, lower_bound=ClosedBound(0)) elif factor == 1: warnings.warn( "Color adjustment factor is 1.0, this will not make changes to the image.", diff --git a/tests/safeds/data/image/containers/test_image.py b/tests/safeds/data/image/containers/test_image.py index 79af2a8e1..3e8494cfb 100644 --- a/tests/safeds/data/image/containers/test_image.py +++ b/tests/safeds/data/image/containers/test_image.py @@ -5,6 +5,7 @@ from safeds.data.image.containers import Image from safeds.data.image.typing import ImageFormat from safeds.data.tabular.containers import Table +from safeds.exceptions import OutOfBoundsError from tests.helpers import resolve_resource_path @@ -280,7 +281,7 @@ def test_should_not_adjust_contrast(self) -> None: def test_should_raise(self) -> None: image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) - with pytest.raises(ValueError, match="Contrast factor has to be 0 or bigger"): + with pytest.raises(OutOfBoundsError, match=r"-1 is not inside \[0, \u221e\)."): image.adjust_contrast(-1) @@ -304,7 +305,7 @@ def test_should_not_brighten(self) -> None: def test_should_raise(self) -> None: image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) - with pytest.raises(ValueError, match="Brightness factor has to be 0 or bigger"): + with pytest.raises(OutOfBoundsError, match=r"-1 is not inside \[0, \u221e\)."): image.adjust_brightness(-1) @@ -361,7 +362,7 @@ def test_should_adjust_colors(self, image: Image, factor: float, expected: Image ids=["negative"], ) def test_should_throw(self, image: Image, factor: float) -> None: - with pytest.raises(ValueError, match="Color factor has to be 0 or bigger."): + with pytest.raises(OutOfBoundsError, match=rf"{factor} is not inside \[0, \u221e\)."): image.adjust_color_balance(factor) @pytest.mark.parametrize( From eab7354728a0c302f3f150b75eeadc3849b623e8 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 9 Jul 2023 10:03:26 +0000 Subject: [PATCH 29/77] style: apply automated linter fixes --- src/safeds/data/image/containers/_image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/safeds/data/image/containers/_image.py b/src/safeds/data/image/containers/_image.py index dc9d3da3a..19213698c 100644 --- a/src/safeds/data/image/containers/_image.py +++ b/src/safeds/data/image/containers/_image.py @@ -10,6 +10,7 @@ from PIL import ImageEnhance, ImageFilter, ImageOps from PIL.Image import Image as PillowImage from PIL.Image import open as open_image + from safeds.data.image.typing import ImageFormat from safeds.exceptions import ClosedBound, OutOfBoundsError From 0c5a23dedaf2b42daa19811e2bd092bda90f7f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 12:59:42 +0200 Subject: [PATCH 30/77] Use ValueError instead of NotImplementedError --- src/safeds/exceptions/_generic.py | 8 +++++--- tests/safeds/exceptions/test_out_of_bounds_error.py | 13 +++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index a2e9db702..c704b553d 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -22,15 +22,17 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( upper_bound is None or isinstance(upper_bound, Infinity) ): - raise NotImplementedError("Illegal interval: Value cannot be out of bounds if there are no bounds.") + raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") if lower_bound is None: lower_bound = MinInfinity() if upper_bound is None: upper_bound = Infinity() if upper_bound._value < lower_bound._value: - raise NotImplementedError("Illegal interval: The upper bound cannot be less than the lower bound.") + raise ValueError( + "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound." + ) elif lower_bound.check_lower_bound(actual) and upper_bound.check_upper_bound(actual): - raise NotImplementedError( + raise ValueError( "Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds.", ) super().__init__(f"{actual} is not inside {lower_bound.str_lower_bound()}, {upper_bound.str_upper_bound()}.") diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 440c6da92..ebaebc9bd 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -41,16 +41,17 @@ def test_should_raise_out_of_bounds_error( upper_bound is None or isinstance(upper_bound, Infinity) ): with pytest.raises( - NotImplementedError, - match=r"Illegal interval: Value cannot be out of bounds if there are no bounds.", + ValueError, + match=r"Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given\.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # All tests: Check interval where lower > upper: if lower_bound is not None and upper_bound is not None: with pytest.raises( - NotImplementedError, - match=r"Illegal interval: The upper bound cannot be less than the lower bound.", + ValueError, + match=r"Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower " + r"bound\.", ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: @@ -58,8 +59,8 @@ def test_should_raise_out_of_bounds_error( upper_bound is None or upper_bound.check_upper_bound(actual) ): with pytest.raises( - NotImplementedError, - match=r"Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds.", + ValueError, + match=r"Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds\.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return From c41eb07ccdfd63eabecf640991ef200814c6b43a Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 9 Jul 2023 11:01:52 +0000 Subject: [PATCH 31/77] style: apply automated linter fixes --- src/safeds/exceptions/_generic.py | 2 +- tests/safeds/exceptions/test_out_of_bounds_error.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index c704b553d..0ff60ddbd 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -29,7 +29,7 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou upper_bound = Infinity() if upper_bound._value < lower_bound._value: raise ValueError( - "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound." + "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound.", ) elif lower_bound.check_lower_bound(actual) and upper_bound.check_upper_bound(actual): raise ValueError( diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index ebaebc9bd..2495caf85 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -51,7 +51,7 @@ def test_should_raise_out_of_bounds_error( with pytest.raises( ValueError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower " - r"bound\.", + r"bound\.", ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: From cb8de4bf129179629871d29658e07a24404e6b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:17:20 +0200 Subject: [PATCH 32/77] Make Infinity classed private --- src/safeds/exceptions/__init__.py | 8 ++++---- src/safeds/exceptions/_generic.py | 16 +++++++-------- .../exceptions/test_out_of_bounds_error.py | 20 ++++++++----------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/safeds/exceptions/__init__.py b/src/safeds/exceptions/__init__.py index 03613598c..07eb22a98 100644 --- a/src/safeds/exceptions/__init__.py +++ b/src/safeds/exceptions/__init__.py @@ -16,10 +16,10 @@ from safeds.exceptions._generic import ( Bound, ClosedBound, - Infinity, - MinInfinity, OpenBound, OutOfBoundsError, + _Infinity, + _MinInfinity, ) from safeds.exceptions._ml import ( DatasetContainsTargetError, @@ -57,7 +57,7 @@ # Other "Bound", "ClosedBound", - "Infinity", - "MinInfinity", + "_Infinity", + "_MinInfinity", "OpenBound", ] diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index c704b553d..c06e2d29c 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -19,14 +19,12 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou upper_bound: Bound | None The upper bound of the expected range. """ - if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( - upper_bound is None or isinstance(upper_bound, Infinity) - ): + if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") - if lower_bound is None: - lower_bound = MinInfinity() - if upper_bound is None: - upper_bound = Infinity() + elif lower_bound is None: + lower_bound = _MinInfinity() + elif upper_bound is None: + upper_bound = _Infinity() if upper_bound._value < lower_bound._value: raise ValueError( "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound." @@ -115,7 +113,7 @@ def check_upper_bound(self, value: float) -> bool: return value < self._value -class Infinity(OpenBound): +class _Infinity(OpenBound): """An infinite or unrestricted upper Bound.""" def __init__(self) -> None: @@ -134,7 +132,7 @@ def check_upper_bound(self, _: float) -> bool: return True -class MinInfinity(OpenBound): +class _MinInfinity(OpenBound): """An infinite or unrestricted lower Bound.""" def __init__(self) -> None: diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index ebaebc9bd..f221cd6a1 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -1,7 +1,7 @@ import re import pytest -from safeds.exceptions import Bound, ClosedBound, Infinity, MinInfinity, OpenBound, OutOfBoundsError +from safeds.exceptions import Bound, ClosedBound, OpenBound, OutOfBoundsError, _Infinity, _MinInfinity @pytest.mark.parametrize( @@ -14,20 +14,18 @@ [ (ClosedBound(-1), "[-1"), (OpenBound(-1), "(-1"), - (MinInfinity(), "(-\u221e"), (None, "(-\u221e"), ], - ids=["lb_closed_-1", "lb_open_-1", "lb_open_inf", "lb_none"], + ids=["lb_closed_-1", "lb_open_-1", "lb_none"], ) @pytest.mark.parametrize( ("upper_bound", "match_upper"), [ (ClosedBound(1), "1]"), (OpenBound(1), "1)"), - (Infinity(), "\u221e)"), (None, "\u221e)"), ], - ids=["ub_closed_-1", "ub_open_-1", "ub_open_inf", "ub_none"], + ids=["ub_closed_-1", "ub_open_-1", "ub_none"], ) def test_should_raise_out_of_bounds_error( actual: float, @@ -37,9 +35,7 @@ def test_should_raise_out_of_bounds_error( match_upper: str, ) -> None: # Check (-inf, inf) interval: - if (lower_bound is None or isinstance(lower_bound, MinInfinity)) and ( - upper_bound is None or isinstance(upper_bound, Infinity) - ): + if lower_bound is None and upper_bound is None: with pytest.raises( ValueError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given\.", @@ -83,10 +79,10 @@ def test_should_raise_out_of_bounds_error( (2, False, OpenBound(2), True), (2, False, OpenBound(1), False), (2, False, OpenBound(3), True), - (2, False, Infinity(), True), - (2, True, Infinity(), False), - (2, True, MinInfinity(), True), - (2, False, MinInfinity(), False), + (2, False, _Infinity(), True), + (2, True, _Infinity(), False), + (2, True, _MinInfinity(), True), + (2, False, _MinInfinity(), False), ], ids=[ "ex_false-close_2-upper", From 076558adfaf49b0d2a302c9b603f9a6245bb7337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:26:34 +0200 Subject: [PATCH 33/77] Create property for value to silence warnings --- src/safeds/exceptions/_generic.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 3f694a3e5..15880b840 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -25,7 +25,7 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou lower_bound = _MinInfinity() elif upper_bound is None: upper_bound = _Infinity() - if upper_bound._value < lower_bound._value: + if upper_bound.value < lower_bound.value: raise ValueError( "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound.", ) @@ -44,7 +44,7 @@ def __init__(self, value: float): def __str__(self) -> str: """Get a string representation of the concrete value of the Bound.""" - return str(self._value) + return str(self.value) @abstractmethod def str_lower_bound(self) -> str: @@ -62,6 +62,11 @@ def check_lower_bound(self, value: float) -> bool: def check_upper_bound(self, value: float) -> bool: """Check that a value does not exceed the Bound on the upper side.""" + @property + def value(self): + """Get the actual value of the Bound.""" + return self._value + class ClosedBound(Bound): """A closed Bound, i.e. the value on the border belongs to the range.""" From bd54cbc1606cd96ccaa098594b2429d71108f0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:31:27 +0200 Subject: [PATCH 34/77] Add type hint for value property --- src/safeds/exceptions/_generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 15880b840..8b20c280f 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -63,8 +63,8 @@ def check_upper_bound(self, value: float) -> bool: """Check that a value does not exceed the Bound on the upper side.""" @property - def value(self): - """Get the actual value of the Bound.""" + def value(self) -> float: + """Get the concrete value of the Bound.""" return self._value From 1928ce21ee6448d8c8c30c796ae02c8704575f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:35:42 +0200 Subject: [PATCH 35/77] Add additional type hints inside __init__ function Hopefully, this makes the linter understand that the local variables cannot be None anymore. --- src/safeds/exceptions/_generic.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 8b20c280f..38d69d857 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -21,10 +21,8 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou """ if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") - elif lower_bound is None: - lower_bound = _MinInfinity() - elif upper_bound is None: - upper_bound = _Infinity() + lower_bound: Bound = lower_bound if lower_bound is not None else _MinInfinity + upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity if upper_bound.value < lower_bound.value: raise ValueError( "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound.", From 1a79d70cab821e945e609d4ac4e7742c331221b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:39:06 +0200 Subject: [PATCH 36/77] Fix unintended class call instead of constructor --- src/safeds/exceptions/_generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 38d69d857..763b64813 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -21,8 +21,8 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou """ if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") - lower_bound: Bound = lower_bound if lower_bound is not None else _MinInfinity - upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity + lower_bound: Bound = lower_bound if lower_bound is not None else _MinInfinity() + upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity() if upper_bound.value < lower_bound.value: raise ValueError( "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound.", From 6a2c51684d9871feafd032a316501b4d4debd21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:44:23 +0200 Subject: [PATCH 37/77] Use local variable with different name --- src/safeds/exceptions/_generic.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 763b64813..0cb8a63c0 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -21,17 +21,18 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou """ if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") - lower_bound: Bound = lower_bound if lower_bound is not None else _MinInfinity() - upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity() - if upper_bound.value < lower_bound.value: + # Use local variables with stricter types to help static analysis: + _lower_bound: Bound = lower_bound if lower_bound is not None else _MinInfinity() + _upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity() + if _upper_bound.value < _lower_bound.value: raise ValueError( "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound.", ) - elif lower_bound.check_lower_bound(actual) and upper_bound.check_upper_bound(actual): + elif _lower_bound.check_lower_bound(actual) and _upper_bound.check_upper_bound(actual): raise ValueError( "Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds.", ) - super().__init__(f"{actual} is not inside {lower_bound.str_lower_bound()}, {upper_bound.str_upper_bound()}.") + super().__init__(f"{actual} is not inside {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}.") class Bound(ABC): From 7407ee2bde02fdaa86b3611eb25943d3a06d3563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:31:53 +0200 Subject: [PATCH 38/77] Remove private classes from __init__.py --- src/safeds/exceptions/__init__.py | 4 ---- tests/safeds/exceptions/test_out_of_bounds_error.py | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/safeds/exceptions/__init__.py b/src/safeds/exceptions/__init__.py index 07eb22a98..dddba06ce 100644 --- a/src/safeds/exceptions/__init__.py +++ b/src/safeds/exceptions/__init__.py @@ -18,8 +18,6 @@ ClosedBound, OpenBound, OutOfBoundsError, - _Infinity, - _MinInfinity, ) from safeds.exceptions._ml import ( DatasetContainsTargetError, @@ -57,7 +55,5 @@ # Other "Bound", "ClosedBound", - "_Infinity", - "_MinInfinity", "OpenBound", ] diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 43d24ece1..5d11fe39c 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -1,7 +1,8 @@ import re import pytest -from safeds.exceptions import Bound, ClosedBound, OpenBound, OutOfBoundsError, _Infinity, _MinInfinity +from safeds.exceptions import Bound, ClosedBound, OpenBound, OutOfBoundsError +from safeds.exceptions._generic import _Infinity, _MinInfinity @pytest.mark.parametrize( From 4ab61c228cf28e0eb16e715a2c1cef166c3bf18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 17:49:59 +0200 Subject: [PATCH 39/77] Improve warning messages for incorrect bounds --- src/safeds/exceptions/_generic.py | 6 ++++-- tests/safeds/exceptions/test_out_of_bounds_error.py | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 0cb8a63c0..80634b58d 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -26,11 +26,13 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou _upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity() if _upper_bound.value < _lower_bound.value: raise ValueError( - "Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower bound.", + f"Illegal interval: Attempting to raise OutOfBoundsError, but given upper bound {_upper_bound} is " + f"actually less than given lower bound {_lower_bound}.", ) elif _lower_bound.check_lower_bound(actual) and _upper_bound.check_upper_bound(actual): raise ValueError( - "Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds.", + f"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually outside " + f"given interval {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}.", ) super().__init__(f"{actual} is not inside {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}.") diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 5d11fe39c..34cd43454 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -47,8 +47,8 @@ def test_should_raise_out_of_bounds_error( if lower_bound is not None and upper_bound is not None: with pytest.raises( ValueError, - match=r"Illegal interval: Attempting to raise OutOfBoundsError, but upper bound is less than the lower " - r"bound\.", + match=r"Illegal interval: Attempting to raise OutOfBoundsError, but given upper bound .+ is actually less " + r"than given lower bound .+\.", ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: @@ -57,7 +57,8 @@ def test_should_raise_out_of_bounds_error( ): with pytest.raises( ValueError, - match=r"Illegal interval: Attempting to raise OutOfBoundsError, but value is not out of bounds\.", + match=rf"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually outside" + rf" given interval [\[(].+,.+[\])]\.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return From 14474e015962327edc953eed3e197ce2e220275b Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 9 Jul 2023 15:51:58 +0000 Subject: [PATCH 40/77] style: apply automated linter fixes --- src/safeds/exceptions/_generic.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 80634b58d..a99767904 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -26,13 +26,17 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou _upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity() if _upper_bound.value < _lower_bound.value: raise ValueError( - f"Illegal interval: Attempting to raise OutOfBoundsError, but given upper bound {_upper_bound} is " - f"actually less than given lower bound {_lower_bound}.", + ( + f"Illegal interval: Attempting to raise OutOfBoundsError, but given upper bound {_upper_bound} is " + f"actually less than given lower bound {_lower_bound}." + ), ) elif _lower_bound.check_lower_bound(actual) and _upper_bound.check_upper_bound(actual): raise ValueError( - f"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually outside " - f"given interval {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}.", + ( + f"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually" + f" outside given interval {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}." + ), ) super().__init__(f"{actual} is not inside {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}.") From 42ef8b59d7b515eb927f5d22f07307adbf8396d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 18:00:18 +0200 Subject: [PATCH 41/77] Document ValueError in docstring --- src/safeds/exceptions/_generic.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index a99767904..2dbfcb17f 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -8,7 +8,7 @@ class OutOfBoundsError(ValueError): def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): """ - Initialize an OutOfBoundError. + Initialize an OutOfBoundsError. Parameters ---------- @@ -18,6 +18,11 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou The lower bound of the expected range. upper_bound: Bound | None The upper bound of the expected range. + + Raises + ------ + ValueError + If upper_bound < lower_bound or if actual does not lie outside the given interval. """ if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") From ed983b8bc559067b8da06b9a2fc02bd6837a2aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:32:49 +0200 Subject: [PATCH 42/77] Remove Infinity classes --- src/safeds/exceptions/_generic.py | 59 +++++-------------- .../exceptions/test_out_of_bounds_error.py | 13 ++-- 2 files changed, 21 insertions(+), 51 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 2dbfcb17f..76a7d4284 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -27,8 +27,8 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") # Use local variables with stricter types to help static analysis: - _lower_bound: Bound = lower_bound if lower_bound is not None else _MinInfinity() - _upper_bound: Bound = upper_bound if upper_bound is not None else _Infinity() + _lower_bound: Bound = lower_bound if lower_bound is not None else OpenBound(float("-inf")) + _upper_bound: Bound = upper_bound if upper_bound is not None else OpenBound(float("inf")) if _upper_bound.value < _lower_bound.value: raise ValueError( ( @@ -94,11 +94,11 @@ def str_upper_bound(self) -> str: def check_lower_bound(self, value: float) -> bool: """Check that a value is not strictly lower than the ClosedBound.""" - return value >= self._value + return value >= self.value def check_upper_bound(self, value: float) -> bool: """Check that a value is not strictly higher than the ClosedBound.""" - return value <= self._value + return value <= self.value class OpenBound(Bound): @@ -111,6 +111,15 @@ class OpenBound(Bound): def __init__(self, value: float): super().__init__(value) + def __str__(self) -> str: + """Get a string representation of the concrete value of the Bound.""" + if self.value == float("-inf"): + return "-\u221e" + elif self.value == float("inf"): + return "\u221e" + else: + return super().__str__() + def str_lower_bound(self) -> str: """Get a string representation of the OpenBound as the lower Bound of an interval.""" return f"({self}" @@ -121,46 +130,8 @@ def str_upper_bound(self) -> str: def check_lower_bound(self, value: float) -> bool: """Check that a value is not lower or equal to the Bound.""" - return value > self._value + return value > self.value def check_upper_bound(self, value: float) -> bool: """Check that a value is not higher ot equal to the Bound.""" - return value < self._value - - -class _Infinity(OpenBound): - """An infinite or unrestricted upper Bound.""" - - def __init__(self) -> None: - super().__init__(float("inf")) - - def __str__(self) -> str: - """Get a string representation of the concrete value of the Bound.""" - return "\u221e" - - def check_lower_bound(self, _: float) -> bool: - """Check that a value does not exceed the unrestricted Bound on the lower side. Always False.""" - return False - - def check_upper_bound(self, _: float) -> bool: - """Check that a value does not exceed the unrestricted Bound on the upper side. Always true.""" - return True - - -class _MinInfinity(OpenBound): - """An infinite or unrestricted lower Bound.""" - - def __init__(self) -> None: - super().__init__(float("-inf")) - - def __str__(self) -> str: - """Get a string representation of the concrete value of the Bound.""" - return "-\u221e" - - def check_lower_bound(self, _: float) -> bool: - """Check that a value does not exceed the unrestricted Bound on the lower side. Always true.""" - return True - - def check_upper_bound(self, _: float) -> bool: - """Check that a value does not exceed the unrestricted Bound on the upper side. Always false.""" - return False + return value < self.value diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 34cd43454..c04f9eeed 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -2,13 +2,12 @@ import pytest from safeds.exceptions import Bound, ClosedBound, OpenBound, OutOfBoundsError -from safeds.exceptions._generic import _Infinity, _MinInfinity @pytest.mark.parametrize( "actual", - [0, 1, -1, 2, -2, float("inf"), float("-inf")], - ids=["0", "1", "-1", "2", "-2", "inf", "-inf"], + [0, 1, -1, 2, -2], + ids=["0", "1", "-1", "2", "-2"], ) @pytest.mark.parametrize( ("lower_bound", "match_lower"), @@ -81,10 +80,10 @@ def test_should_raise_out_of_bounds_error( (2, False, OpenBound(2), True), (2, False, OpenBound(1), False), (2, False, OpenBound(3), True), - (2, False, _Infinity(), True), - (2, True, _Infinity(), False), - (2, True, _MinInfinity(), True), - (2, False, _MinInfinity(), False), + (2, False, OpenBound(float("inf")), True), + (2, True, OpenBound(float("inf")), False), + (2, True, OpenBound(float("-inf")), True), + (2, False, OpenBound(float("-inf")), False), ], ids=[ "ex_false-close_2-upper", From 979819069bbe4ef9518399385e0fee12efc9bef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:37:30 +0200 Subject: [PATCH 43/77] Make string and check methods private --- src/safeds/exceptions/_generic.py | 30 +++++++++---------- .../exceptions/test_out_of_bounds_error.py | 8 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 76a7d4284..6a5fdebeb 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -36,14 +36,14 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou f"actually less than given lower bound {_lower_bound}." ), ) - elif _lower_bound.check_lower_bound(actual) and _upper_bound.check_upper_bound(actual): + elif _lower_bound._check_lower_bound(actual) and _upper_bound._check_upper_bound(actual): raise ValueError( ( f"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually" - f" outside given interval {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}." + f" outside given interval {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}." ), ) - super().__init__(f"{actual} is not inside {_lower_bound.str_lower_bound()}, {_upper_bound.str_upper_bound()}.") + super().__init__(f"{actual} is not inside {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}.") class Bound(ABC): @@ -57,19 +57,19 @@ def __str__(self) -> str: return str(self.value) @abstractmethod - def str_lower_bound(self) -> str: + def _str_lower_bound(self) -> str: """Get a string representation of the Bound as the lower Bound of an interval.""" @abstractmethod - def str_upper_bound(self) -> str: + def _str_upper_bound(self) -> str: """Get a string representation of the Bound as the upper Bound of an interval.""" @abstractmethod - def check_lower_bound(self, value: float) -> bool: + def _check_lower_bound(self, value: float) -> bool: """Check that a value does not exceed the Bound on the lower side.""" @abstractmethod - def check_upper_bound(self, value: float) -> bool: + def _check_upper_bound(self, value: float) -> bool: """Check that a value does not exceed the Bound on the upper side.""" @property @@ -84,19 +84,19 @@ class ClosedBound(Bound): def __init__(self, value: float): super().__init__(value) - def str_lower_bound(self) -> str: + def _str_lower_bound(self) -> str: """Get a string representation of the ClosedBound as the lower Bound of an interval.""" return f"[{self}" - def str_upper_bound(self) -> str: + def _str_upper_bound(self) -> str: """Get a string representation of the ClosedBound as the upper Bound of an interval.""" return f"{self}]" - def check_lower_bound(self, value: float) -> bool: + def _check_lower_bound(self, value: float) -> bool: """Check that a value is not strictly lower than the ClosedBound.""" return value >= self.value - def check_upper_bound(self, value: float) -> bool: + def _check_upper_bound(self, value: float) -> bool: """Check that a value is not strictly higher than the ClosedBound.""" return value <= self.value @@ -120,18 +120,18 @@ def __str__(self) -> str: else: return super().__str__() - def str_lower_bound(self) -> str: + def _str_lower_bound(self) -> str: """Get a string representation of the OpenBound as the lower Bound of an interval.""" return f"({self}" - def str_upper_bound(self) -> str: + def _str_upper_bound(self) -> str: """Get a string representation of the OpenBound as the upper Bound of an interval.""" return f"{self})" - def check_lower_bound(self, value: float) -> bool: + def _check_lower_bound(self, value: float) -> bool: """Check that a value is not lower or equal to the Bound.""" return value > self.value - def check_upper_bound(self, value: float) -> bool: + def _check_upper_bound(self, value: float) -> bool: """Check that a value is not higher ot equal to the Bound.""" return value < self.value diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index c04f9eeed..5553a6dda 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -51,8 +51,8 @@ def test_should_raise_out_of_bounds_error( ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: - if (lower_bound is None or lower_bound.check_lower_bound(actual)) and ( - upper_bound is None or upper_bound.check_upper_bound(actual) + if (lower_bound is None or lower_bound._check_lower_bound(actual)) and ( + upper_bound is None or upper_bound._check_upper_bound(actual) ): with pytest.raises( ValueError, @@ -107,6 +107,6 @@ def test_should_return_true_if_value_in_bounds( lower_bound: bool, ) -> None: if lower_bound: - assert expected_value == bound.check_lower_bound(value) + assert expected_value == bound._check_lower_bound(value) else: - assert expected_value == bound.check_upper_bound(value) + assert expected_value == bound._check_upper_bound(value) From 5ef248a8ba8f292b5116a9ff27b39372dd448b08 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 9 Jul 2023 20:39:31 +0000 Subject: [PATCH 44/77] style: apply automated linter fixes --- src/safeds/exceptions/_generic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 6a5fdebeb..3be553b24 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -43,7 +43,9 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou f" outside given interval {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}." ), ) - super().__init__(f"{actual} is not inside {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}.") + super().__init__( + f"{actual} is not inside {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}.", + ) class Bound(ABC): From d616bccb465115d05ed70249e1b021479136792f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:16:58 +0200 Subject: [PATCH 45/77] Check for nan in Bound constructor --- src/safeds/exceptions/_generic.py | 4 ++++ tests/safeds/exceptions/test_out_of_bounds_error.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 3be553b24..e9c1beb0c 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -2,6 +2,8 @@ from abc import ABC, abstractmethod +from numpy import isnan + class OutOfBoundsError(ValueError): """A generic exception that can be used to signal that a (float) value is outside its expected range.""" @@ -52,6 +54,8 @@ class Bound(ABC): """Abstract base class for (lower or upper) Bounds on a float value.""" def __init__(self, value: float): + if isnan(value): + raise ValueError("Bound must be a number or +/-infinity, not nan.") self._value = value def __str__(self) -> str: diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 5553a6dda..fca071117 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -110,3 +110,11 @@ def test_should_return_true_if_value_in_bounds( assert expected_value == bound._check_lower_bound(value) else: assert expected_value == bound._check_upper_bound(value) + + +# Cannot parametrize because we cannot iterate over classes. +def test_should_raise_value_error_because_nan() -> None: + with pytest.raises(ValueError, match=r"Bound must be a number or \+\/\-infinity, not nan\."): + ClosedBound(float("nan")) + with pytest.raises(ValueError, match=r"Bound must be a number or \+\/\-infinity, not nan\."): + OpenBound(float("nan")) From 6f69b16c25a6a1fca08b5e1837050f7e51a51f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:57:43 +0200 Subject: [PATCH 46/77] Apply suggestions from code review * Fixed docstrings * Updated nan and inf checks --- src/safeds/exceptions/_generic.py | 155 +++++++++++++++--- .../exceptions/test_out_of_bounds_error.py | 19 ++- 2 files changed, 146 insertions(+), 28 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index e9c1beb0c..b9fab0680 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -6,7 +6,18 @@ class OutOfBoundsError(ValueError): - """A generic exception that can be used to signal that a (float) value is outside its expected range.""" + """ + A generic exception that can be used to signal that a (float) value is outside its expected range. + + Parameters + ---------- + actual: float + The actual value that is outside its expected range. + lower_bound: Bound | None + The lower bound of the expected range. + upper_bound: Bound | None + The upper bound of the expected range. + """ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): """ @@ -51,11 +62,31 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou class Bound(ABC): - """Abstract base class for (lower or upper) Bounds on a float value.""" + """ + Abstract base class for (lower or upper) Bounds on a float value. + + Parameters + ---------- + value: float + The value of the Bound. + """ def __init__(self, value: float): + """ + Initialize a Bound. + + Parameters + ---------- + value: float + The value of the Bound. + + Raises + ------ + ValueError + If value is nan or if value is +/-inf and the Bound type does not allow for infinite Bounds. + """ if isnan(value): - raise ValueError("Bound must be a number or +/-infinity, not nan.") + raise ValueError("Bound must be a real number, not nan.") self._value = value def __str__(self) -> str: @@ -71,12 +102,26 @@ def _str_upper_bound(self) -> str: """Get a string representation of the Bound as the upper Bound of an interval.""" @abstractmethod - def _check_lower_bound(self, value: float) -> bool: - """Check that a value does not exceed the Bound on the lower side.""" + def _check_lower_bound(self, actual: float) -> bool: + """ + Check that a value does not exceed the Bound on the lower side. + + Parameters + ---------- + actual: float + The actual value that should be checked for not exceeding the Bound. + """ @abstractmethod - def _check_upper_bound(self, value: float) -> bool: - """Check that a value does not exceed the Bound on the upper side.""" + def _check_upper_bound(self, actual: float) -> bool: + """ + Check that a value does not exceed the Bound on the upper side. + + Parameters + ---------- + actual: float + The actual value that should be checked for not exceeding the Bound. + """ @property def value(self) -> float: @@ -85,9 +130,31 @@ def value(self) -> float: class ClosedBound(Bound): - """A closed Bound, i.e. the value on the border belongs to the range.""" + """ + A closed Bound, i.e. the value on the border belongs to the range. + + Parameters + ---------- + value: float + The value of the Bound. + """ def __init__(self, value: float): + """ + Initialize a ClosedBound. + + Parameters + ---------- + value: float + The value of the ClosedBound. + + Raises + ------ + ValueError + If value is nan or if value is +/-inf. + """ + if value == float("-inf") or value == float("inf"): + raise ValueError("ClosedBound must be a real number, not +/-inf.") super().__init__(value) def _str_lower_bound(self) -> str: @@ -98,27 +165,57 @@ def _str_upper_bound(self) -> str: """Get a string representation of the ClosedBound as the upper Bound of an interval.""" return f"{self}]" - def _check_lower_bound(self, value: float) -> bool: - """Check that a value is not strictly lower than the ClosedBound.""" - return value >= self.value + def _check_lower_bound(self, actual: float) -> bool: + """ + Check that a value is not strictly lower than the ClosedBound. + + Parameters + ---------- + actual: float + The actual value that should be checked for not exceeding the Bound. + """ + return actual >= self.value + + def _check_upper_bound(self, actual: float) -> bool: + """ + Check that a value is not strictly higher than the ClosedBound. - def _check_upper_bound(self, value: float) -> bool: - """Check that a value is not strictly higher than the ClosedBound.""" - return value <= self.value + Parameters + ---------- + actual: float + The actual value that should be checked for not exceeding the Bound. + """ + return actual <= self.value class OpenBound(Bound): """ An open Bound, i.e. the value on the border does not belong to the range. - May be infinite (unbounded). + Parameters + ---------- + value: float + The value of the OpenBound. """ def __init__(self, value: float): + """ + Initialize an OpenBound. + + Parameters + ---------- + value: float + The value of the OpenBound. + + Raises + ------ + ValueError + If value is nan. + """ super().__init__(value) def __str__(self) -> str: - """Get a string representation of the concrete value of the Bound.""" + """Get a string representation of the concrete value of the OpenBound.""" if self.value == float("-inf"): return "-\u221e" elif self.value == float("inf"): @@ -134,10 +231,24 @@ def _str_upper_bound(self) -> str: """Get a string representation of the OpenBound as the upper Bound of an interval.""" return f"{self})" - def _check_lower_bound(self, value: float) -> bool: - """Check that a value is not lower or equal to the Bound.""" - return value > self.value + def _check_lower_bound(self, actual: float) -> bool: + """ + Check that a value is not lower or equal to the OpenBound. + + Parameters + ---------- + actual: float + The actual value that should be checked for not exceeding the Bound. + """ + return actual > self.value + + def _check_upper_bound(self, actual: float) -> bool: + """ + Check that a value is not higher or equal to the OpenBound. - def _check_upper_bound(self, value: float) -> bool: - """Check that a value is not higher ot equal to the Bound.""" - return value < self.value + Parameters + ---------- + actual: float + The actual value that should be checked for not exceeding the Bound. + """ + return actual < self.value diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index fca071117..2735cf3d3 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -1,6 +1,7 @@ import re import pytest +from numpy import isnan from safeds.exceptions import Bound, ClosedBound, OpenBound, OutOfBoundsError @@ -112,9 +113,15 @@ def test_should_return_true_if_value_in_bounds( assert expected_value == bound._check_upper_bound(value) -# Cannot parametrize because we cannot iterate over classes. -def test_should_raise_value_error_because_nan() -> None: - with pytest.raises(ValueError, match=r"Bound must be a number or \+\/\-infinity, not nan\."): - ClosedBound(float("nan")) - with pytest.raises(ValueError, match=r"Bound must be a number or \+\/\-infinity, not nan\."): - OpenBound(float("nan")) +@pytest.mark.parametrize( + "value", [float("nan"), float("-inf"), float("inf")], ids=["nan", "neg_inf", "inf"] +) +def test_should_raise_value_error(value: float) -> None: + if isnan(value): + with pytest.raises(ValueError, match="Bound must be a real number, not nan."): + ClosedBound(value) + with pytest.raises(ValueError, match="Bound must be a real number, not nan."): + OpenBound(value) + else: + with pytest.raises(ValueError, match=r"ClosedBound must be a real number, not \+\/\-inf\."): + ClosedBound(value) From 915d652707537ad2f10351f5ae52fdba88b2d68c Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:59:56 +0000 Subject: [PATCH 47/77] style: apply automated linter fixes --- tests/safeds/exceptions/test_out_of_bounds_error.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 2735cf3d3..774e1d4c1 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -113,9 +113,7 @@ def test_should_return_true_if_value_in_bounds( assert expected_value == bound._check_upper_bound(value) -@pytest.mark.parametrize( - "value", [float("nan"), float("-inf"), float("inf")], ids=["nan", "neg_inf", "inf"] -) +@pytest.mark.parametrize("value", [float("nan"), float("-inf"), float("inf")], ids=["nan", "neg_inf", "inf"]) def test_should_raise_value_error(value: float) -> None: if isnan(value): with pytest.raises(ValueError, match="Bound must be a real number, not nan."): From f2b82e9d2a42adb0bff2db78dce9e337a7d053f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 00:46:17 +0200 Subject: [PATCH 48/77] Use OutOfBoundsError in Discretizer Also updated error message to allow for variable name. --- src/safeds/data/tabular/transformation/_discretizer.py | 7 ++++--- src/safeds/exceptions/_generic.py | 7 +++++-- .../safeds/data/tabular/transformation/test_discretizer.py | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_discretizer.py b/src/safeds/data/tabular/transformation/_discretizer.py index 38a6cf0f0..bcfa57ff7 100644 --- a/src/safeds/data/tabular/transformation/_discretizer.py +++ b/src/safeds/data/tabular/transformation/_discretizer.py @@ -4,7 +4,8 @@ from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation._table_transformer import TableTransformer -from safeds.exceptions import NonNumericColumnError, TransformerNotFittedError, UnknownColumnNameError +from safeds.exceptions import NonNumericColumnError, TransformerNotFittedError, UnknownColumnNameError, \ + OutOfBoundsError, ClosedBound class Discretizer(TableTransformer): @@ -18,7 +19,7 @@ class Discretizer(TableTransformer): Raises ------ - ValueError + OutOfBoundsError If the given number_of_bins is less than 2. """ @@ -27,7 +28,7 @@ def __init__(self, number_of_bins: float = 5): self._wrapped_transformer: sk_KBinsDiscretizer | None = None if number_of_bins < 2: - raise ValueError("Parameter 'number_of_bins' must be >= 2.") + raise OutOfBoundsError(number_of_bins, name="number_of_bins", lower_bound=ClosedBound(2)) self._number_of_bins = number_of_bins def fit(self, table: Table, column_names: list[str] | None) -> Discretizer: diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index b9fab0680..5bb3b7c76 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -19,7 +19,7 @@ class OutOfBoundsError(ValueError): The upper bound of the expected range. """ - def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bound: Bound | None = None): + def __init__(self, actual: float, *, name: str | None = None, lower_bound: Bound | None = None, upper_bound: Bound | None = None): """ Initialize an OutOfBoundsError. @@ -27,6 +27,8 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou ---------- actual: float The actual value that is outside its expected range. + name: str | None + The name of the offending variable, for a better error message. lower_bound: Bound | None The lower bound of the expected range. upper_bound: Bound | None @@ -56,8 +58,9 @@ def __init__(self, actual: float, *, lower_bound: Bound | None = None, upper_bou f" outside given interval {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}." ), ) + full_variable_name = actual if name is None else f"{name} (={actual})" super().__init__( - f"{actual} is not inside {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}.", + f"{full_variable_name} is not inside {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}.", ) diff --git a/tests/safeds/data/tabular/transformation/test_discretizer.py b/tests/safeds/data/tabular/transformation/test_discretizer.py index b4a69971b..5e7e7acdd 100644 --- a/tests/safeds/data/tabular/transformation/test_discretizer.py +++ b/tests/safeds/data/tabular/transformation/test_discretizer.py @@ -1,12 +1,12 @@ import pytest from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import Discretizer -from safeds.exceptions import NonNumericColumnError, TransformerNotFittedError, UnknownColumnNameError +from safeds.exceptions import NonNumericColumnError, TransformerNotFittedError, UnknownColumnNameError, OutOfBoundsError class TestInit: def test_should_raise_value_error(self) -> None: - with pytest.raises(ValueError, match="Parameter 'number_of_bins' must be >= 2."): + with pytest.raises(OutOfBoundsError, match=r"number_of_bins \(=1\) is not inside \[2, \u221e\)\."): _ = Discretizer(1) From 4cf2547f7c35ae2a2b04cbb7755c57e70021f69a Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:49:11 +0000 Subject: [PATCH 49/77] style: apply automated linter fixes --- src/safeds/data/tabular/transformation/_discretizer.py | 9 +++++++-- src/safeds/exceptions/_generic.py | 9 ++++++++- .../data/tabular/transformation/test_discretizer.py | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_discretizer.py b/src/safeds/data/tabular/transformation/_discretizer.py index bcfa57ff7..581130c55 100644 --- a/src/safeds/data/tabular/transformation/_discretizer.py +++ b/src/safeds/data/tabular/transformation/_discretizer.py @@ -4,8 +4,13 @@ from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation._table_transformer import TableTransformer -from safeds.exceptions import NonNumericColumnError, TransformerNotFittedError, UnknownColumnNameError, \ - OutOfBoundsError, ClosedBound +from safeds.exceptions import ( + ClosedBound, + NonNumericColumnError, + OutOfBoundsError, + TransformerNotFittedError, + UnknownColumnNameError, +) class Discretizer(TableTransformer): diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 5bb3b7c76..1872ea566 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -19,7 +19,14 @@ class OutOfBoundsError(ValueError): The upper bound of the expected range. """ - def __init__(self, actual: float, *, name: str | None = None, lower_bound: Bound | None = None, upper_bound: Bound | None = None): + def __init__( + self, + actual: float, + *, + name: str | None = None, + lower_bound: Bound | None = None, + upper_bound: Bound | None = None, + ): """ Initialize an OutOfBoundsError. diff --git a/tests/safeds/data/tabular/transformation/test_discretizer.py b/tests/safeds/data/tabular/transformation/test_discretizer.py index 5e7e7acdd..dc9ea7d13 100644 --- a/tests/safeds/data/tabular/transformation/test_discretizer.py +++ b/tests/safeds/data/tabular/transformation/test_discretizer.py @@ -1,7 +1,7 @@ import pytest from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import Discretizer -from safeds.exceptions import NonNumericColumnError, TransformerNotFittedError, UnknownColumnNameError, OutOfBoundsError +from safeds.exceptions import NonNumericColumnError, OutOfBoundsError, TransformerNotFittedError, UnknownColumnNameError class TestInit: From 79be629d4be86120738652a5fd2a407c12b9516e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 00:51:33 +0200 Subject: [PATCH 50/77] Update Images to use name parameter --- src/safeds/data/image/containers/_image.py | 6 +++--- tests/safeds/data/image/containers/test_image.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/safeds/data/image/containers/_image.py b/src/safeds/data/image/containers/_image.py index 19213698c..cccab981f 100644 --- a/src/safeds/data/image/containers/_image.py +++ b/src/safeds/data/image/containers/_image.py @@ -294,7 +294,7 @@ def adjust_brightness(self, factor: float) -> Image: The Image with adjusted brightness. """ if factor < 0: - raise OutOfBoundsError(factor, lower_bound=ClosedBound(0)) + raise OutOfBoundsError(factor, name="factor", lower_bound=ClosedBound(0)) elif factor == 1: warnings.warn( "Brightness adjustment factor is 1.0, this will not make changes to the image.", @@ -323,7 +323,7 @@ def adjust_contrast(self, factor: float) -> Image: New image with adjusted contrast. """ if factor < 0: - raise OutOfBoundsError(factor, lower_bound=ClosedBound(0)) + raise OutOfBoundsError(factor, name="factor", lower_bound=ClosedBound(0)) elif factor == 1: warnings.warn( "Contrast adjustment factor is 1.0, this will not make changes to the image.", @@ -353,7 +353,7 @@ def adjust_color_balance(self, factor: float) -> Image: The new, adjusted image. """ if factor < 0: - raise OutOfBoundsError(factor, lower_bound=ClosedBound(0)) + raise OutOfBoundsError(factor, name="factor", lower_bound=ClosedBound(0)) elif factor == 1: warnings.warn( "Color adjustment factor is 1.0, this will not make changes to the image.", diff --git a/tests/safeds/data/image/containers/test_image.py b/tests/safeds/data/image/containers/test_image.py index 3e8494cfb..9bb66dace 100644 --- a/tests/safeds/data/image/containers/test_image.py +++ b/tests/safeds/data/image/containers/test_image.py @@ -281,7 +281,7 @@ def test_should_not_adjust_contrast(self) -> None: def test_should_raise(self) -> None: image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) - with pytest.raises(OutOfBoundsError, match=r"-1 is not inside \[0, \u221e\)."): + with pytest.raises(OutOfBoundsError, match=r"factor \(=-1\) is not inside \[0, \u221e\)."): image.adjust_contrast(-1) @@ -305,7 +305,7 @@ def test_should_not_brighten(self) -> None: def test_should_raise(self) -> None: image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) - with pytest.raises(OutOfBoundsError, match=r"-1 is not inside \[0, \u221e\)."): + with pytest.raises(OutOfBoundsError, match=r"factor \(=-1\) is not inside \[0, \u221e\)."): image.adjust_brightness(-1) @@ -362,7 +362,7 @@ def test_should_adjust_colors(self, image: Image, factor: float, expected: Image ids=["negative"], ) def test_should_throw(self, image: Image, factor: float) -> None: - with pytest.raises(OutOfBoundsError, match=rf"{factor} is not inside \[0, \u221e\)."): + with pytest.raises(OutOfBoundsError, match=rf"factor \(={factor}\) is not inside \[0, \u221e\)."): image.adjust_color_balance(factor) @pytest.mark.parametrize( From 5c37bb5c105df5d0009faec44cbca69dc55e735d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 01:00:19 +0200 Subject: [PATCH 51/77] Add tests for name parameter --- tests/safeds/exceptions/test_out_of_bounds_error.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 774e1d4c1..a985e2885 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -10,6 +10,9 @@ [0, 1, -1, 2, -2], ids=["0", "1", "-1", "2", "-2"], ) +@pytest.mark.parametrize( + "variable_name", ["test_variable"], ids=["test_variable"] +) @pytest.mark.parametrize( ("lower_bound", "match_lower"), [ @@ -30,6 +33,7 @@ ) def test_should_raise_out_of_bounds_error( actual: float, + variable_name: str | None, lower_bound: Bound | None, upper_bound: Bound | None, match_lower: str, @@ -68,6 +72,11 @@ def test_should_raise_out_of_bounds_error( match=rf"{actual} is not inside {re.escape(match_lower)}, {re.escape(match_upper)}.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + with pytest.raises( + OutOfBoundsError, + match=rf"{variable_name} \(={actual}\) is not inside {re.escape(match_lower)}, {re.escape(match_upper)}.", + ): + raise OutOfBoundsError(actual, name=variable_name, lower_bound=lower_bound, upper_bound=upper_bound) @pytest.mark.parametrize( From aff945b9cae4f19e60bf90cdcdbf4a0e12d1d6e2 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:02:06 +0000 Subject: [PATCH 52/77] style: apply automated linter fixes --- tests/safeds/exceptions/test_out_of_bounds_error.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index a985e2885..e9d923e83 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -10,9 +10,7 @@ [0, 1, -1, 2, -2], ids=["0", "1", "-1", "2", "-2"], ) -@pytest.mark.parametrize( - "variable_name", ["test_variable"], ids=["test_variable"] -) +@pytest.mark.parametrize("variable_name", ["test_variable"], ids=["test_variable"]) @pytest.mark.parametrize( ("lower_bound", "match_lower"), [ From 3885a435709c0e574a910c55998656542e340843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 01:03:15 +0200 Subject: [PATCH 53/77] Update class docstring to include name parameter --- src/safeds/exceptions/_generic.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 1872ea566..cb3fdd2ba 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -13,6 +13,8 @@ class OutOfBoundsError(ValueError): ---------- actual: float The actual value that is outside its expected range. + name: str | None + The name of the offending variable. lower_bound: Bound | None The lower bound of the expected range. upper_bound: Bound | None @@ -35,7 +37,7 @@ def __init__( actual: float The actual value that is outside its expected range. name: str | None - The name of the offending variable, for a better error message. + The name of the offending variable. lower_bound: Bound | None The lower bound of the expected range. upper_bound: Bound | None From fada030541ed3df8e9906566770734a7171c4b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 19:03:22 +0200 Subject: [PATCH 54/77] Add nan and inf checks --- src/safeds/exceptions/_generic.py | 18 +++++++++++++----- .../exceptions/test_out_of_bounds_error.py | 19 +++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index cb3fdd2ba..df5272b48 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -16,9 +16,9 @@ class OutOfBoundsError(ValueError): name: str | None The name of the offending variable. lower_bound: Bound | None - The lower bound of the expected range. + The lower bound of the expected range. Use None if there is no lower Bound. upper_bound: Bound | None - The upper bound of the expected range. + The upper bound of the expected range. Use None if there is no upper Bound. """ def __init__( @@ -39,20 +39,28 @@ def __init__( name: str | None The name of the offending variable. lower_bound: Bound | None - The lower bound of the expected range. + The lower bound of the expected range. Use None if there is no lower Bound. upper_bound: Bound | None - The upper bound of the expected range. + The upper bound of the expected range. Use None if there is no upper Bound. Raises ------ ValueError - If upper_bound < lower_bound or if actual does not lie outside the given interval. + * If one of the given Bounds is +/-inf. (For infinite Bounds, pass None instead.) + * If one of the given Bounds is nan. + * If upper_bound < lower_bound. + * If actual does not lie outside the given interval. + * If actual is not a real number. """ if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") + if isnan(actual) or actual == float("inf") or actual == float("-inf"): + raise ValueError("Attempting to raise OutOfBoundsError with actual value not being a real number.") # Use local variables with stricter types to help static analysis: _lower_bound: Bound = lower_bound if lower_bound is not None else OpenBound(float("-inf")) _upper_bound: Bound = upper_bound if upper_bound is not None else OpenBound(float("inf")) + if lower_bound == OpenBound(float("-inf")) or upper_bound == OpenBound(float("-inf")) or lower_bound == OpenBound(float("inf")) or upper_bound == OpenBound(float("inf")): + raise ValueError("Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.") if _upper_bound.value < _lower_bound.value: raise ValueError( ( diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index e9d923e83..cdafb0024 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -17,8 +17,9 @@ (ClosedBound(-1), "[-1"), (OpenBound(-1), "(-1"), (None, "(-\u221e"), + (OpenBound(float("-inf")), "(-\u221e"), ], - ids=["lb_closed_-1", "lb_open_-1", "lb_none"], + ids=["lb_closed_-1", "lb_open_-1", "lb_none", "lb_neg_inf"], ) @pytest.mark.parametrize( ("upper_bound", "match_upper"), @@ -26,8 +27,9 @@ (ClosedBound(1), "1]"), (OpenBound(1), "1)"), (None, "\u221e)"), + (OpenBound(float("inf")), "\u221e)"), ], - ids=["ub_closed_-1", "ub_open_-1", "ub_none"], + ids=["ub_closed_-1", "ub_open_-1", "ub_none", "ub_inf"], ) def test_should_raise_out_of_bounds_error( actual: float, @@ -45,6 +47,12 @@ def test_should_raise_out_of_bounds_error( ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return + # Check if infinity was passed instead of None: + if lower_bound == OpenBound(float("-inf")) or lower_bound == OpenBound(float("inf")) or upper_bound == OpenBound(float("-inf")) or upper_bound == OpenBound(float("inf")): + with pytest.raises(ValueError, match="Illegal interval: Lower and upper bounds must be real numbers, or None " + "if unbounded."): + raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) + return # All tests: Check interval where lower > upper: if lower_bound is not None and upper_bound is not None: with pytest.raises( @@ -130,3 +138,10 @@ def test_should_raise_value_error(value: float) -> None: else: with pytest.raises(ValueError, match=r"ClosedBound must be a real number, not \+\/\-inf\."): ClosedBound(value) + + +@pytest.mark.parametrize("actual", [float("nan"), float("-inf"), float("inf")], ids=["nan", "neg_inf", "inf"]) +def test_should_raise_value_error_because_invalid_actual(actual: float) -> None: + with pytest.raises(ValueError, match="Attempting to raise OutOfBoundsError with actual value not being a real " + "number."): + raise OutOfBoundsError(actual, lower_bound=ClosedBound(-1), upper_bound=ClosedBound(1)) From ff44d26c53b51d2668be7004aa512753f0d405df Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 17:05:33 +0000 Subject: [PATCH 55/77] style: apply automated linter fixes --- src/safeds/exceptions/_generic.py | 7 ++++++- .../exceptions/test_out_of_bounds_error.py | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index df5272b48..0ce0ad9d6 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -59,7 +59,12 @@ def __init__( # Use local variables with stricter types to help static analysis: _lower_bound: Bound = lower_bound if lower_bound is not None else OpenBound(float("-inf")) _upper_bound: Bound = upper_bound if upper_bound is not None else OpenBound(float("inf")) - if lower_bound == OpenBound(float("-inf")) or upper_bound == OpenBound(float("-inf")) or lower_bound == OpenBound(float("inf")) or upper_bound == OpenBound(float("inf")): + if ( + lower_bound == OpenBound(float("-inf")) + or upper_bound == OpenBound(float("-inf")) + or lower_bound == OpenBound(float("inf")) + or upper_bound == OpenBound(float("inf")) + ): raise ValueError("Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.") if _upper_bound.value < _lower_bound.value: raise ValueError( diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index cdafb0024..09a3358fe 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -48,9 +48,15 @@ def test_should_raise_out_of_bounds_error( raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # Check if infinity was passed instead of None: - if lower_bound == OpenBound(float("-inf")) or lower_bound == OpenBound(float("inf")) or upper_bound == OpenBound(float("-inf")) or upper_bound == OpenBound(float("inf")): - with pytest.raises(ValueError, match="Illegal interval: Lower and upper bounds must be real numbers, or None " - "if unbounded."): + if ( + lower_bound == OpenBound(float("-inf")) + or lower_bound == OpenBound(float("inf")) + or upper_bound == OpenBound(float("-inf")) + or upper_bound == OpenBound(float("inf")) + ): + with pytest.raises( + ValueError, match="Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.", + ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # All tests: Check interval where lower > upper: @@ -142,6 +148,7 @@ def test_should_raise_value_error(value: float) -> None: @pytest.mark.parametrize("actual", [float("nan"), float("-inf"), float("inf")], ids=["nan", "neg_inf", "inf"]) def test_should_raise_value_error_because_invalid_actual(actual: float) -> None: - with pytest.raises(ValueError, match="Attempting to raise OutOfBoundsError with actual value not being a real " - "number."): + with pytest.raises( + ValueError, match="Attempting to raise OutOfBoundsError with actual value not being a real number.", + ): raise OutOfBoundsError(actual, lower_bound=ClosedBound(-1), upper_bound=ClosedBound(1)) From abf1d556e2f3d9cc00edff8e76e70e1e786a1f35 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 17:07:15 +0000 Subject: [PATCH 56/77] style: apply automated linter fixes --- tests/safeds/exceptions/test_out_of_bounds_error.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 09a3358fe..b71cc3f4b 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -55,7 +55,8 @@ def test_should_raise_out_of_bounds_error( or upper_bound == OpenBound(float("inf")) ): with pytest.raises( - ValueError, match="Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.", + ValueError, + match="Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return @@ -149,6 +150,7 @@ def test_should_raise_value_error(value: float) -> None: @pytest.mark.parametrize("actual", [float("nan"), float("-inf"), float("inf")], ids=["nan", "neg_inf", "inf"]) def test_should_raise_value_error_because_invalid_actual(actual: float) -> None: with pytest.raises( - ValueError, match="Attempting to raise OutOfBoundsError with actual value not being a real number.", + ValueError, + match="Attempting to raise OutOfBoundsError with actual value not being a real number.", ): raise OutOfBoundsError(actual, lower_bound=ClosedBound(-1), upper_bound=ClosedBound(1)) From 61736019265db734cf064465484fbc8df0c1a3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 19:11:35 +0200 Subject: [PATCH 57/77] Remove unnecessary checks --- tests/safeds/exceptions/test_out_of_bounds_error.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index b71cc3f4b..45ae113b1 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -48,12 +48,7 @@ def test_should_raise_out_of_bounds_error( raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # Check if infinity was passed instead of None: - if ( - lower_bound == OpenBound(float("-inf")) - or lower_bound == OpenBound(float("inf")) - or upper_bound == OpenBound(float("-inf")) - or upper_bound == OpenBound(float("inf")) - ): + if lower_bound == OpenBound(float("-inf")) or upper_bound == OpenBound(float("inf")): with pytest.raises( ValueError, match="Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.", From 4a069ce4bec2dd8405600a20d7a37f2b6ffe87a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 21:43:09 +0200 Subject: [PATCH 58/77] Fix equality comparisons --- src/safeds/exceptions/_generic.py | 28 +++++++++---------- .../exceptions/test_out_of_bounds_error.py | 14 ++++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index 0ce0ad9d6..b1cd875c6 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod -from numpy import isnan +from numpy import isinf, isnan class OutOfBoundsError(ValueError): @@ -52,20 +52,18 @@ def __init__( * If actual does not lie outside the given interval. * If actual is not a real number. """ + # Validate bound parameters: if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") - if isnan(actual) or actual == float("inf") or actual == float("-inf"): + if (lower_bound is not None and isinf(lower_bound.value)) or (upper_bound is not None and isinf(upper_bound.value)): + raise ValueError("Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.") + # Validate actual parameter: + if isnan(actual) or actual == float("-inf") or actual == float("inf"): raise ValueError("Attempting to raise OutOfBoundsError with actual value not being a real number.") # Use local variables with stricter types to help static analysis: _lower_bound: Bound = lower_bound if lower_bound is not None else OpenBound(float("-inf")) _upper_bound: Bound = upper_bound if upper_bound is not None else OpenBound(float("inf")) - if ( - lower_bound == OpenBound(float("-inf")) - or upper_bound == OpenBound(float("-inf")) - or lower_bound == OpenBound(float("inf")) - or upper_bound == OpenBound(float("inf")) - ): - raise ValueError("Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.") + # Check bounds: if _upper_bound.value < _lower_bound.value: raise ValueError( ( @@ -73,6 +71,7 @@ def __init__( f"actually less than given lower bound {_lower_bound}." ), ) + # Check that actual is indeed outside the interval: elif _lower_bound._check_lower_bound(actual) and _upper_bound._check_upper_bound(actual): raise ValueError( ( @@ -80,6 +79,7 @@ def __init__( f" outside given interval {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}." ), ) + # Raise the actual exception: full_variable_name = actual if name is None else f"{name} (={actual})" super().__init__( f"{full_variable_name} is not inside {_lower_bound._str_lower_bound()}, {_upper_bound._str_upper_bound()}.", @@ -118,6 +118,11 @@ def __str__(self) -> str: """Get a string representation of the concrete value of the Bound.""" return str(self.value) + @property + def value(self) -> float: + """Get the concrete value of the Bound.""" + return self._value + @abstractmethod def _str_lower_bound(self) -> str: """Get a string representation of the Bound as the lower Bound of an interval.""" @@ -148,11 +153,6 @@ def _check_upper_bound(self, actual: float) -> bool: The actual value that should be checked for not exceeding the Bound. """ - @property - def value(self) -> float: - """Get the concrete value of the Bound.""" - return self._value - class ClosedBound(Bound): """ diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 45ae113b1..90e7d3ca2 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -1,7 +1,7 @@ import re import pytest -from numpy import isnan +from numpy import isinf, isnan from safeds.exceptions import Bound, ClosedBound, OpenBound, OutOfBoundsError @@ -18,8 +18,9 @@ (OpenBound(-1), "(-1"), (None, "(-\u221e"), (OpenBound(float("-inf")), "(-\u221e"), + (OpenBound(float("inf")), "(\u221e"), ], - ids=["lb_closed_-1", "lb_open_-1", "lb_none", "lb_neg_inf"], + ids=["lb_closed_-1", "lb_open_-1", "lb_none", "lb_neg_inf", "lb_inf"], ) @pytest.mark.parametrize( ("upper_bound", "match_upper"), @@ -27,9 +28,10 @@ (ClosedBound(1), "1]"), (OpenBound(1), "1)"), (None, "\u221e)"), + (OpenBound(float("-inf")), "-\u221e)"), (OpenBound(float("inf")), "\u221e)"), ], - ids=["ub_closed_-1", "ub_open_-1", "ub_none", "ub_inf"], + ids=["ub_closed_-1", "ub_open_-1", "ub_none", "ub_neg_inf", "ub_inf"], ) def test_should_raise_out_of_bounds_error( actual: float, @@ -48,7 +50,7 @@ def test_should_raise_out_of_bounds_error( raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return # Check if infinity was passed instead of None: - if lower_bound == OpenBound(float("-inf")) or upper_bound == OpenBound(float("inf")): + if (lower_bound is not None and isinf(lower_bound.value)) or (upper_bound is not None and isinf(upper_bound.value)): with pytest.raises( ValueError, match="Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.", @@ -60,7 +62,7 @@ def test_should_raise_out_of_bounds_error( with pytest.raises( ValueError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, but given upper bound .+ is actually less " - r"than given lower bound .+\.", + r"than given lower bound .+\.", ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: @@ -70,7 +72,7 @@ def test_should_raise_out_of_bounds_error( with pytest.raises( ValueError, match=rf"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually outside" - rf" given interval [\[(].+,.+[\])]\.", + rf" given interval [\[(].+,.+[\])]\.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return From 1361f491634e5a2de80d9e9fdfb547b30ac6e70d Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 19:45:06 +0000 Subject: [PATCH 59/77] style: apply automated linter fixes --- src/safeds/exceptions/_generic.py | 4 +++- tests/safeds/exceptions/test_out_of_bounds_error.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index b1cd875c6..ff9981290 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -55,7 +55,9 @@ def __init__( # Validate bound parameters: if lower_bound is None and upper_bound is None: raise ValueError("Illegal interval: Attempting to raise OutOfBoundsError, but no bounds given.") - if (lower_bound is not None and isinf(lower_bound.value)) or (upper_bound is not None and isinf(upper_bound.value)): + if (lower_bound is not None and isinf(lower_bound.value)) or ( + upper_bound is not None and isinf(upper_bound.value) + ): raise ValueError("Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.") # Validate actual parameter: if isnan(actual) or actual == float("-inf") or actual == float("inf"): diff --git a/tests/safeds/exceptions/test_out_of_bounds_error.py b/tests/safeds/exceptions/test_out_of_bounds_error.py index 90e7d3ca2..f78006795 100644 --- a/tests/safeds/exceptions/test_out_of_bounds_error.py +++ b/tests/safeds/exceptions/test_out_of_bounds_error.py @@ -62,7 +62,7 @@ def test_should_raise_out_of_bounds_error( with pytest.raises( ValueError, match=r"Illegal interval: Attempting to raise OutOfBoundsError, but given upper bound .+ is actually less " - r"than given lower bound .+\.", + r"than given lower bound .+\.", ): raise OutOfBoundsError(actual, lower_bound=upper_bound, upper_bound=lower_bound) # Check case where actual value lies inside the interval: @@ -72,7 +72,7 @@ def test_should_raise_out_of_bounds_error( with pytest.raises( ValueError, match=rf"Illegal interval: Attempting to raise OutOfBoundsError, but value {actual} is not actually outside" - rf" given interval [\[(].+,.+[\])]\.", + rf" given interval [\[(].+,.+[\])]\.", ): raise OutOfBoundsError(actual, lower_bound=lower_bound, upper_bound=upper_bound) return From 61f78c200706a1d7c9492eedcfae8fd5bb30a05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 21:48:49 +0200 Subject: [PATCH 60/77] Simplify infinity checks using isinf --- src/safeds/exceptions/_generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/exceptions/_generic.py b/src/safeds/exceptions/_generic.py index ff9981290..b874a4f30 100644 --- a/src/safeds/exceptions/_generic.py +++ b/src/safeds/exceptions/_generic.py @@ -60,7 +60,7 @@ def __init__( ): raise ValueError("Illegal interval: Lower and upper bounds must be real numbers, or None if unbounded.") # Validate actual parameter: - if isnan(actual) or actual == float("-inf") or actual == float("inf"): + if isinf(actual) or isnan(actual): raise ValueError("Attempting to raise OutOfBoundsError with actual value not being a real number.") # Use local variables with stricter types to help static analysis: _lower_bound: Bound = lower_bound if lower_bound is not None else OpenBound(float("-inf")) From 6c62093cb73ba93ffd437aa86a5285ab5a2187e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 22:08:13 +0200 Subject: [PATCH 61/77] Use OutOfBoundsError in AdaBoost classifier --- .../ml/classical/classification/_ada_boost.py | 7 ++++--- .../ml/classical/classification/test_ada_boost.py | 15 +++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/safeds/ml/classical/classification/_ada_boost.py b/src/safeds/ml/classical/classification/_ada_boost.py index 6cde8e4f2..3cc400c54 100644 --- a/src/safeds/ml/classical/classification/_ada_boost.py +++ b/src/safeds/ml/classical/classification/_ada_boost.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import AdaBoostClassifier as sk_AdaBoostClassifier from safeds.ml.classical._util_sklearn import fit, predict @@ -31,7 +32,7 @@ class AdaBoost(Classifier): Raises ------ - ValueError + OutOfBoundsError If `maximum_number_of_learners` or `learning_rate` are less than or equal to 0 """ @@ -44,9 +45,9 @@ def __init__( ) -> None: # Validation if maximum_number_of_learners <= 0: - raise ValueError("The parameter 'maximum_number_of_learners' has to be grater than 0.") + raise OutOfBoundsError(maximum_number_of_learners, name="maximum_number_of_learners", lower_bound=OpenBound(0)) if learning_rate <= 0: - raise ValueError("The parameter 'learning_rate' has to be greater than 0.") + raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) # Hyperparameters self._learner = learner diff --git a/tests/safeds/ml/classical/classification/test_ada_boost.py b/tests/safeds/ml/classical/classification/test_ada_boost.py index 1ecec8f4c..b6281e6ea 100644 --- a/tests/safeds/ml/classical/classification/test_ada_boost.py +++ b/tests/safeds/ml/classical/classification/test_ada_boost.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.classification import AdaBoost @@ -32,9 +33,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.n_estimators == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'maximum_number_of_learners' has to be grater than 0."): - AdaBoost(maximum_number_of_learners=-1) + @pytest.mark.parametrize("maximum_number_of_learners", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, maximum_number_of_learners: int) -> None: + with pytest.raises(OutOfBoundsError, match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \(0, \u221e\)\."): + AdaBoost(maximum_number_of_learners=maximum_number_of_learners) class TestLearningRate: @@ -47,6 +49,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.learning_rate == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'learning_rate' has to be greater than 0."): - AdaBoost(learning_rate=-1) + @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: + with pytest.raises(OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\."): + AdaBoost(learning_rate=learning_rate) From a23062fc587f19f353f8b176c53ef9fb0690966d Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:10:12 +0000 Subject: [PATCH 62/77] style: apply automated linter fixes --- src/safeds/ml/classical/classification/_ada_boost.py | 6 ++++-- .../safeds/ml/classical/classification/test_ada_boost.py | 9 +++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/safeds/ml/classical/classification/_ada_boost.py b/src/safeds/ml/classical/classification/_ada_boost.py index 3cc400c54..2fb9413fb 100644 --- a/src/safeds/ml/classical/classification/_ada_boost.py +++ b/src/safeds/ml/classical/classification/_ada_boost.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import AdaBoostClassifier as sk_AdaBoostClassifier +from safeds.exceptions import OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier @@ -45,7 +45,9 @@ def __init__( ) -> None: # Validation if maximum_number_of_learners <= 0: - raise OutOfBoundsError(maximum_number_of_learners, name="maximum_number_of_learners", lower_bound=OpenBound(0)) + raise OutOfBoundsError( + maximum_number_of_learners, name="maximum_number_of_learners", lower_bound=OpenBound(0), + ) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/tests/safeds/ml/classical/classification/test_ada_boost.py b/tests/safeds/ml/classical/classification/test_ada_boost.py index b6281e6ea..4254e8e22 100644 --- a/tests/safeds/ml/classical/classification/test_ada_boost.py +++ b/tests/safeds/ml/classical/classification/test_ada_boost.py @@ -35,7 +35,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: @pytest.mark.parametrize("maximum_number_of_learners", [-1, 0], ids=["minus_one", "zero"]) def test_should_raise_if_less_than_or_equal_to_0(self, maximum_number_of_learners: int) -> None: - with pytest.raises(OutOfBoundsError, match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \(0, \u221e\)\."): + with pytest.raises( + OutOfBoundsError, + match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \(0, \u221e\)\.", + ): AdaBoost(maximum_number_of_learners=maximum_number_of_learners) @@ -51,5 +54,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: - with pytest.raises(OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\."): + with pytest.raises( + OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", + ): AdaBoost(learning_rate=learning_rate) From 0a5322785c095ad91c48fc774344b6efaf8a9bed Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:12:06 +0000 Subject: [PATCH 63/77] style: apply automated linter fixes --- src/safeds/ml/classical/classification/_ada_boost.py | 4 +++- tests/safeds/ml/classical/classification/test_ada_boost.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/safeds/ml/classical/classification/_ada_boost.py b/src/safeds/ml/classical/classification/_ada_boost.py index 2fb9413fb..299445af3 100644 --- a/src/safeds/ml/classical/classification/_ada_boost.py +++ b/src/safeds/ml/classical/classification/_ada_boost.py @@ -46,7 +46,9 @@ def __init__( # Validation if maximum_number_of_learners <= 0: raise OutOfBoundsError( - maximum_number_of_learners, name="maximum_number_of_learners", lower_bound=OpenBound(0), + maximum_number_of_learners, + name="maximum_number_of_learners", + lower_bound=OpenBound(0), ) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/tests/safeds/ml/classical/classification/test_ada_boost.py b/tests/safeds/ml/classical/classification/test_ada_boost.py index 4254e8e22..aae8956ef 100644 --- a/tests/safeds/ml/classical/classification/test_ada_boost.py +++ b/tests/safeds/ml/classical/classification/test_ada_boost.py @@ -55,6 +55,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: with pytest.raises( - OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", + OutOfBoundsError, + match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", ): AdaBoost(learning_rate=learning_rate) From c2e0f10803725846074224872a369586f645b9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 22:22:28 +0200 Subject: [PATCH 64/77] Use new error in gradient boosting classifier --- .../classification/_gradient_boosting.py | 9 +++++---- .../classification/test_gradient_boosting.py | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/safeds/ml/classical/classification/_gradient_boosting.py b/src/safeds/ml/classical/classification/_gradient_boosting.py index 23804b3ca..ab67ac1b4 100644 --- a/src/safeds/ml/classical/classification/_gradient_boosting.py +++ b/src/safeds/ml/classical/classification/_gradient_boosting.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import GradientBoostingClassifier as sk_GradientBoostingClassifier from safeds.ml.classical._util_sklearn import fit, predict @@ -29,16 +30,16 @@ class GradientBoosting(Classifier): Raises ------ - ValueError - If `number_of_trees` is less than or equal to 0 or `learning_rate` is non-positive. + OutOfBoundsError + If `number_of_trees` or `learning_rate` is less than or equal to 0. """ def __init__(self, *, number_of_trees: int = 100, learning_rate: float = 0.1) -> None: # Validation if number_of_trees <= 0: - raise ValueError("The parameter 'number_of_trees' has to be greater than 0.") + raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=OpenBound(0)) if learning_rate <= 0: - raise ValueError("The parameter 'learning_rate' has to be greater than 0.") + raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) # Hyperparameters self._number_of_trees = number_of_trees diff --git a/tests/safeds/ml/classical/classification/test_gradient_boosting.py b/tests/safeds/ml/classical/classification/test_gradient_boosting.py index e30564036..d3b505143 100644 --- a/tests/safeds/ml/classical/classification/test_gradient_boosting.py +++ b/tests/safeds/ml/classical/classification/test_gradient_boosting.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.classification import GradientBoosting @@ -19,9 +20,13 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.n_estimators == 2 - def test_should_raise_if_less_than_1(self) -> None: - with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."): - GradientBoosting(number_of_trees=0) + @pytest.mark.parametrize("number_of_trees", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_1(self, number_of_trees: int) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"number_of_trees \(={number_of_trees}\) is not inside \(0, \u221e\)\.", + ): + GradientBoosting(number_of_trees=number_of_trees) class TestLearningRate: @@ -34,6 +39,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.learning_rate == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'learning_rate' has to be greater than 0."): - GradientBoosting(learning_rate=-1) + @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: + with pytest.raises(OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\."): + GradientBoosting(learning_rate=learning_rate) From aa3df4ac4af6d90d4ebceb5939f754602a4dafe9 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:25:52 +0000 Subject: [PATCH 65/77] style: apply automated linter fixes --- src/safeds/ml/classical/classification/_gradient_boosting.py | 2 +- .../ml/classical/classification/test_gradient_boosting.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/safeds/ml/classical/classification/_gradient_boosting.py b/src/safeds/ml/classical/classification/_gradient_boosting.py index ab67ac1b4..3a646adc9 100644 --- a/src/safeds/ml/classical/classification/_gradient_boosting.py +++ b/src/safeds/ml/classical/classification/_gradient_boosting.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import GradientBoostingClassifier as sk_GradientBoostingClassifier +from safeds.exceptions import OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier diff --git a/tests/safeds/ml/classical/classification/test_gradient_boosting.py b/tests/safeds/ml/classical/classification/test_gradient_boosting.py index d3b505143..43a3ec9dd 100644 --- a/tests/safeds/ml/classical/classification/test_gradient_boosting.py +++ b/tests/safeds/ml/classical/classification/test_gradient_boosting.py @@ -41,5 +41,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: - with pytest.raises(OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\."): + with pytest.raises( + OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", + ): GradientBoosting(learning_rate=learning_rate) From babda19fe6f19f9ef53bf1d3ea45b10ddf272245 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:27:33 +0000 Subject: [PATCH 66/77] style: apply automated linter fixes --- .../ml/classical/classification/test_gradient_boosting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/safeds/ml/classical/classification/test_gradient_boosting.py b/tests/safeds/ml/classical/classification/test_gradient_boosting.py index 43a3ec9dd..eb6d6ca50 100644 --- a/tests/safeds/ml/classical/classification/test_gradient_boosting.py +++ b/tests/safeds/ml/classical/classification/test_gradient_boosting.py @@ -42,6 +42,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: with pytest.raises( - OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", + OutOfBoundsError, + match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", ): GradientBoosting(learning_rate=learning_rate) From 74ea122a573b1282e00e1bb184cc7629acb30963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 22:39:40 +0200 Subject: [PATCH 67/77] Use new error for nearest_neighbors classifier --- .../classical/classification/_k_nearest_neighbors.py | 6 +++--- .../classification/test_k_nearest_neighbors.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py index d7ff20362..bcda9e23b 100644 --- a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsClassifier as sk_KNeighborsClassifier -from safeds.exceptions import DatasetMissesDataError +from safeds.exceptions import DatasetMissesDataError, OutOfBoundsError, OpenBound from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier @@ -27,14 +27,14 @@ class KNearestNeighbors(Classifier): Raises ------ - ValueError + OutOfBoundsError If `number_of_neighbors` is less than or equal to 0. """ def __init__(self, number_of_neighbors: int) -> None: # Validation if number_of_neighbors <= 0: - raise ValueError("The parameter 'number_of_neighbors' has to be greater than 0.") + raise OutOfBoundsError(number_of_neighbors, name="number_of_neighbors", lower_bound=OpenBound(0)) # Hyperparameters self._number_of_neighbors = number_of_neighbors diff --git a/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py b/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py index 982f80184..5d8c8aecc 100644 --- a/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py +++ b/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.classification import KNearestNeighbors @@ -19,9 +20,13 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.n_neighbors == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'number_of_neighbors' has to be greater than 0."): - KNearestNeighbors(number_of_neighbors=-1) + @pytest.mark.parametrize("number_of_neighbors", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, number_of_neighbors: int) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"number_of_neighbors \(={number_of_neighbors}\) is not inside \(0, \u221e\)\.", + ): + KNearestNeighbors(number_of_neighbors=number_of_neighbors) def test_should_raise_if_greater_than_sample_size(self, training_set: TaggedTable) -> None: with pytest.raises(ValueError, match="has to be less than or equal to the sample size"): From 6a42065fea51032f616ee2b5ea16cf2512ccb91a Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:42:55 +0000 Subject: [PATCH 68/77] style: apply automated linter fixes --- src/safeds/ml/classical/classification/_k_nearest_neighbors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py index bcda9e23b..c90923614 100644 --- a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsClassifier as sk_KNeighborsClassifier -from safeds.exceptions import DatasetMissesDataError, OutOfBoundsError, OpenBound +from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier From acd0d851ff629c90727360924603efc9ceb87ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 23:32:25 +0200 Subject: [PATCH 69/77] Use OutOfBoundsError for RandomForest classifier --- .../ml/classical/classification/_random_forest.py | 5 +++-- .../ml/classical/classification/test_random_forest.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/safeds/ml/classical/classification/_random_forest.py b/src/safeds/ml/classical/classification/_random_forest.py index 850c5296b..5174a8c4a 100644 --- a/src/safeds/ml/classical/classification/_random_forest.py +++ b/src/safeds/ml/classical/classification/_random_forest.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import RandomForestClassifier as sk_RandomForestClassifier from safeds.ml.classical._util_sklearn import fit, predict @@ -24,14 +25,14 @@ class RandomForest(Classifier): Raises ------ - ValueError + OutOfBoundsError If `number_of_trees` is less than or equal to 0. """ def __init__(self, *, number_of_trees: int = 100) -> None: # Validation if number_of_trees < 1: - raise ValueError("The parameter 'number_of_trees' has to be greater than 0.") + raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=OpenBound(0)) # Hyperparameters self._number_of_trees = number_of_trees diff --git a/tests/safeds/ml/classical/classification/test_random_forest.py b/tests/safeds/ml/classical/classification/test_random_forest.py index c6df609cc..852a21554 100644 --- a/tests/safeds/ml/classical/classification/test_random_forest.py +++ b/tests/safeds/ml/classical/classification/test_random_forest.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.classification import RandomForest @@ -19,6 +20,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.n_estimators == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."): - RandomForest(number_of_trees=-1) + @pytest.mark.parametrize("number_of_trees", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, number_of_trees: int) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"number_of_trees \(={number_of_trees}\) is not inside \(0, \u221e\)\.", + ): + RandomForest(number_of_trees=number_of_trees) From de897c97585b9d5b0cf75685fd05536e54bb04e6 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 21:34:26 +0000 Subject: [PATCH 70/77] style: apply automated linter fixes --- src/safeds/ml/classical/classification/_random_forest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/ml/classical/classification/_random_forest.py b/src/safeds/ml/classical/classification/_random_forest.py index 5174a8c4a..545ac8f9b 100644 --- a/src/safeds/ml/classical/classification/_random_forest.py +++ b/src/safeds/ml/classical/classification/_random_forest.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import RandomForestClassifier as sk_RandomForestClassifier +from safeds.exceptions import OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier From 3b3eba11687c3dbc81e48586b804e8be0d1c53d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Mon, 10 Jul 2023 23:52:10 +0200 Subject: [PATCH 71/77] Use OutOfBoundsError for SVM and inner classes Also fixed type hints for float hyperparameters in tests. --- .../classification/_support_vector_machine.py | 7 ++++--- .../classical/classification/test_ada_boost.py | 4 ++-- .../classification/test_gradient_boosting.py | 4 ++-- .../test_support_vector_machine.py | 18 +++++++++--------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/safeds/ml/classical/classification/_support_vector_machine.py b/src/safeds/ml/classical/classification/_support_vector_machine.py index 57a528d9f..a53149a93 100644 --- a/src/safeds/ml/classical/classification/_support_vector_machine.py +++ b/src/safeds/ml/classical/classification/_support_vector_machine.py @@ -3,6 +3,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, OpenBound, ClosedBound from sklearn.svm import SVC as sk_SVC # noqa: N811 from safeds.ml.classical._util_sklearn import fit, predict @@ -41,7 +42,7 @@ class SupportVectorMachine(Classifier): Raises ------ - ValueError + OutOfBoundsError If `c` is less than or equal to 0. """ @@ -53,7 +54,7 @@ def __init__(self, *, c: float = 1.0, kernel: SupportVectorMachineKernel | None # Hyperparameters if c <= 0: - raise ValueError("The parameter 'c' has to be strictly positive.") + raise OutOfBoundsError(c, name="c", lower_bound=OpenBound(0)) self._c = c self._kernel = kernel @@ -97,7 +98,7 @@ def get_sklearn_kernel(self) -> str: class Polynomial(SupportVectorMachineKernel): def __init__(self, degree: int): if degree < 1: - raise ValueError("The parameter 'degree' has to be greater than or equal to 1.") + raise OutOfBoundsError(degree, name="degree", lower_bound=ClosedBound(1)) self._degree = degree def get_sklearn_kernel(self) -> str: diff --git a/tests/safeds/ml/classical/classification/test_ada_boost.py b/tests/safeds/ml/classical/classification/test_ada_boost.py index aae8956ef..6bf2017ca 100644 --- a/tests/safeds/ml/classical/classification/test_ada_boost.py +++ b/tests/safeds/ml/classical/classification/test_ada_boost.py @@ -52,8 +52,8 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.learning_rate == 2 - @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) - def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: + @pytest.mark.parametrize("learning_rate", [-1.0, 0.0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: float) -> None: with pytest.raises( OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", diff --git a/tests/safeds/ml/classical/classification/test_gradient_boosting.py b/tests/safeds/ml/classical/classification/test_gradient_boosting.py index eb6d6ca50..be3255871 100644 --- a/tests/safeds/ml/classical/classification/test_gradient_boosting.py +++ b/tests/safeds/ml/classical/classification/test_gradient_boosting.py @@ -39,8 +39,8 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.learning_rate == 2 - @pytest.mark.parametrize("learning_rate", [-1, 0], ids=["minus_one", "zero"]) - def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: int) -> None: + @pytest.mark.parametrize("learning_rate", [-1.0, 0.0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: float) -> None: with pytest.raises( OutOfBoundsError, match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", diff --git a/tests/safeds/ml/classical/classification/test_support_vector_machine.py b/tests/safeds/ml/classical/classification/test_support_vector_machine.py index d54b3d895..148ba4542 100644 --- a/tests/safeds/ml/classical/classification/test_support_vector_machine.py +++ b/tests/safeds/ml/classical/classification/test_support_vector_machine.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.classification import SupportVectorMachine @@ -19,12 +20,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_classifier is not None assert fitted_model._wrapped_classifier.C == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises( - ValueError, - match="The parameter 'c' has to be strictly positive.", - ): - SupportVectorMachine(c=-1) + @pytest.mark.parametrize("c", [-1.0, 0.0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, c: float) -> None: + with pytest.raises(OutOfBoundsError, match=rf"c \(={c}\) is not inside \(0, \u221e\)\."): + SupportVectorMachine(c=c) class TestKernel: @@ -45,9 +44,10 @@ def test_should_get_sklearn_kernel_linear(self) -> None: linear_kernel = svm.kernel.get_sklearn_kernel() assert linear_kernel == "linear" - def test_should_raise_if_degree_less_than_1(self) -> None: - with pytest.raises(ValueError, match="The parameter 'degree' has to be greater than or equal to 1."): - SupportVectorMachine.Kernel.Polynomial(degree=0) + @pytest.mark.parametrize("degree", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_degree_less_than_1(self, degree: int) -> None: + with pytest.raises(OutOfBoundsError, match=rf"degree \(={degree}\) is not inside \[1, \u221e\)\."): + SupportVectorMachine.Kernel.Polynomial(degree=degree) def test_should_get_sklearn_kernel_polynomial(self) -> None: svm = SupportVectorMachine(c=2, kernel=SupportVectorMachine.Kernel.Polynomial(degree=2)) From d9019361ce21615dd66205823cb12304eff06024 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 21:55:02 +0000 Subject: [PATCH 72/77] style: apply automated linter fixes --- .../ml/classical/classification/_support_vector_machine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/ml/classical/classification/_support_vector_machine.py b/src/safeds/ml/classical/classification/_support_vector_machine.py index a53149a93..123d72f49 100644 --- a/src/safeds/ml/classical/classification/_support_vector_machine.py +++ b/src/safeds/ml/classical/classification/_support_vector_machine.py @@ -3,9 +3,9 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, OpenBound, ClosedBound from sklearn.svm import SVC as sk_SVC # noqa: N811 +from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from safeds.ml.classical.classification import Classifier From 2dc37975c119df8c97791af8797a80956e5ceb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Tue, 11 Jul 2023 00:42:19 +0200 Subject: [PATCH 73/77] Use OutOfBoundsError in regression models --- .../ml/classical/regression/_ada_boost.py | 10 +++++--- .../regression/_elastic_net_regression.py | 12 ++++++--- .../regression/_gradient_boosting.py | 7 +++--- .../regression/_k_nearest_neighbors.py | 6 ++--- .../classical/regression/_lasso_regression.py | 5 ++-- .../ml/classical/regression/_random_forest.py | 5 ++-- .../classical/regression/_ridge_regression.py | 5 ++-- .../regression/_support_vector_machine.py | 7 +++--- .../ml/classical/regression/test_ada_boost.py | 21 +++++++++++----- .../regression/test_elastic_net_regression.py | 25 +++++++++++-------- .../regression/test_gradient_boosting.py | 21 +++++++++++----- .../regression/test_k_nearest_neighbors.py | 11 +++++--- .../regression/test_lasso_regression.py | 8 +++--- .../regression/test_random_forest.py | 11 +++++--- .../regression/test_ridge_regression.py | 8 +++--- .../regression/test_support_vector_machine.py | 18 ++++++------- 16 files changed, 116 insertions(+), 64 deletions(-) diff --git a/src/safeds/ml/classical/regression/_ada_boost.py b/src/safeds/ml/classical/regression/_ada_boost.py index d4d39bf85..f68711676 100644 --- a/src/safeds/ml/classical/regression/_ada_boost.py +++ b/src/safeds/ml/classical/regression/_ada_boost.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from safeds.exceptions import OpenBound, OutOfBoundsError from sklearn.ensemble import AdaBoostRegressor as sk_AdaBoostRegressor from safeds.ml.classical._util_sklearn import fit, predict @@ -31,7 +32,7 @@ class AdaBoost(Regressor): Raises ------ - ValueError + OutOfBoundsError If `maximum_number_of_learners` or `learning_rate` are less than or equal to 0 """ @@ -44,9 +45,12 @@ def __init__( ) -> None: # Validation if maximum_number_of_learners <= 0: - raise ValueError("The parameter 'maximum_number_of_learners' has to be grater than 0.") + raise OutOfBoundsError( + maximum_number_of_learners, + name="maximum_number_of_learners", lower_bound=OpenBound(0) + ) if learning_rate <= 0: - raise ValueError("The parameter 'learning_rate' has to be greater than 0.") + raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) # Hyperparameters self._learner = learner diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 054bcb3cf..3110141cc 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING from warnings import warn +from safeds.exceptions import ClosedBound, OutOfBoundsError from sklearn.linear_model import ElasticNet as sk_ElasticNet from safeds.ml.classical._util_sklearn import fit, predict @@ -29,14 +30,14 @@ class ElasticNetRegression(Regressor): Raises ------ - ValueError + OutOfBoundsError If `alpha` is negative or `lasso_ratio` is not between 0 and 1. """ def __init__(self, *, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: # Validation if alpha < 0: - raise ValueError("The parameter 'alpha' must be non-negative") + raise OutOfBoundsError(alpha, name="alpha", lower_bound=ClosedBound(0)) if alpha == 0: warn( ( @@ -47,7 +48,12 @@ def __init__(self, *, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: stacklevel=2, ) if lasso_ratio < 0 or lasso_ratio > 1: - raise ValueError("The parameter 'lasso_ratio' must be between 0 and 1.") + raise OutOfBoundsError( + lasso_ratio, + name="lasso_ratio", + lower_bound=ClosedBound(0), + upper_bound=ClosedBound(1) + ) elif lasso_ratio == 0: warnings.warn( ( diff --git a/src/safeds/ml/classical/regression/_gradient_boosting.py b/src/safeds/ml/classical/regression/_gradient_boosting.py index b783f4511..67de10836 100644 --- a/src/safeds/ml/classical/regression/_gradient_boosting.py +++ b/src/safeds/ml/classical/regression/_gradient_boosting.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import GradientBoostingRegressor as sk_GradientBoostingRegressor from safeds.ml.classical._util_sklearn import fit, predict @@ -29,16 +30,16 @@ class GradientBoosting(Regressor): Raises ------ - ValueError + OutOfBoundsError If `number_of_trees` is less than or equal to 0 or `learning_rate` is non-positive. """ def __init__(self, *, number_of_trees: int = 100, learning_rate: float = 0.1) -> None: # Validation if number_of_trees <= 0: - raise ValueError("The parameter 'number_of_trees' has to be greater than 0.") + raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=OpenBound(0)) if learning_rate <= 0: - raise ValueError("The parameter 'learning_rate' has to be greater than 0.") + raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) # Hyperparameters self._number_of_trees = number_of_trees diff --git a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py index 4da871342..37871d334 100644 --- a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsRegressor as sk_KNeighborsRegressor -from safeds.exceptions import DatasetMissesDataError +from safeds.exceptions import DatasetMissesDataError, OutOfBoundsError, OpenBound from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor @@ -27,14 +27,14 @@ class KNearestNeighbors(Regressor): Raises ------ - ValueError + OutOfBoundsError If `number_of_neighbors` is less than or equal to 0. """ def __init__(self, number_of_neighbors: int) -> None: # Validation if number_of_neighbors <= 0: - raise ValueError("The parameter 'number_of_neighbors' has to be greater than 0.") + raise OutOfBoundsError(number_of_neighbors, name="number_of_neighbors", lower_bound=OpenBound(0)) # Hyperparameters self._number_of_neighbors = number_of_neighbors diff --git a/src/safeds/ml/classical/regression/_lasso_regression.py b/src/safeds/ml/classical/regression/_lasso_regression.py index ac238e330..1c7afb2ee 100644 --- a/src/safeds/ml/classical/regression/_lasso_regression.py +++ b/src/safeds/ml/classical/regression/_lasso_regression.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from warnings import warn +from safeds.exceptions import OutOfBoundsError, ClosedBound from sklearn.linear_model import Lasso as sk_Lasso from safeds.ml.classical._util_sklearn import fit, predict @@ -25,14 +26,14 @@ class LassoRegression(Regressor): Raises ------ - ValueError + OutOfBoundsError If `alpha` is negative. """ def __init__(self, *, alpha: float = 1.0) -> None: # Validation if alpha < 0: - raise ValueError("The parameter 'alpha' must be non-negative") + raise OutOfBoundsError(alpha, name="alpha", lower_bound=ClosedBound(0)) if alpha == 0: warn( ( diff --git a/src/safeds/ml/classical/regression/_random_forest.py b/src/safeds/ml/classical/regression/_random_forest.py index 3908592ff..4e14db3cc 100644 --- a/src/safeds/ml/classical/regression/_random_forest.py +++ b/src/safeds/ml/classical/regression/_random_forest.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, ClosedBound from sklearn.ensemble import RandomForestRegressor as sk_RandomForestRegressor from safeds.ml.classical._util_sklearn import fit, predict @@ -24,14 +25,14 @@ class RandomForest(Regressor): Raises ------ - ValueError + OutOfBoundsError If `number_of_trees` is less than or equal to 0. """ def __init__(self, *, number_of_trees: int = 100) -> None: # Validation if number_of_trees < 1: - raise ValueError("The parameter 'number_of_trees' has to be greater than 0.") + raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=ClosedBound(1)) # Hyperparameters self._number_of_trees = number_of_trees diff --git a/src/safeds/ml/classical/regression/_ridge_regression.py b/src/safeds/ml/classical/regression/_ridge_regression.py index b775c4215..937101f7f 100644 --- a/src/safeds/ml/classical/regression/_ridge_regression.py +++ b/src/safeds/ml/classical/regression/_ridge_regression.py @@ -3,6 +3,7 @@ import warnings from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, ClosedBound from sklearn.linear_model import Ridge as sk_Ridge from safeds.ml.classical._util_sklearn import fit, predict @@ -26,14 +27,14 @@ class RidgeRegression(Regressor): Raises ------ - ValueError + OutOfBoundsError If `alpha` is negative. """ def __init__(self, *, alpha: float = 1.0) -> None: # Validation if alpha < 0: - raise ValueError("The parameter 'alpha' must be non-negative") + raise OutOfBoundsError(alpha, name="alpha", lower_bound=ClosedBound(0)) if alpha == 0.0: warnings.warn( ( diff --git a/src/safeds/ml/classical/regression/_support_vector_machine.py b/src/safeds/ml/classical/regression/_support_vector_machine.py index 3fb6fe367..0da868402 100644 --- a/src/safeds/ml/classical/regression/_support_vector_machine.py +++ b/src/safeds/ml/classical/regression/_support_vector_machine.py @@ -3,6 +3,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING +from safeds.exceptions import OutOfBoundsError, OpenBound, ClosedBound from sklearn.svm import SVR as sk_SVR # noqa: N811 from safeds.ml.classical._util_sklearn import fit, predict @@ -41,7 +42,7 @@ class SupportVectorMachine(Regressor): Raises ------ - ValueError + OutOfBoundsError If `c` is less than or equal to 0. """ @@ -53,7 +54,7 @@ def __init__(self, *, c: float = 1.0, kernel: SupportVectorMachineKernel | None # Hyperparameters if c <= 0: - raise ValueError("The parameter 'c' has to be strictly positive.") + raise OutOfBoundsError(c, name="c", lower_bound=OpenBound(0)) self._c = c self._kernel = kernel @@ -97,7 +98,7 @@ def get_sklearn_kernel(self) -> str: class Polynomial(SupportVectorMachineKernel): def __init__(self, degree: int): if degree < 1: - raise ValueError("The parameter 'degree' has to be greater than or equal to 1.") + raise OutOfBoundsError(degree, name="degree", lower_bound=ClosedBound(1)) self._degree = degree def get_sklearn_kernel(self) -> str: diff --git a/tests/safeds/ml/classical/regression/test_ada_boost.py b/tests/safeds/ml/classical/regression/test_ada_boost.py index 73f3511bb..0758dcd83 100644 --- a/tests/safeds/ml/classical/regression/test_ada_boost.py +++ b/tests/safeds/ml/classical/regression/test_ada_boost.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import AdaBoost @@ -32,9 +33,13 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.n_estimators == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'maximum_number_of_learners' has to be grater than 0."): - AdaBoost(maximum_number_of_learners=-1) + @pytest.mark.parametrize("maximum_number_of_learners", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, maximum_number_of_learners: int) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \(0, \u221e\)\.", + ): + AdaBoost(maximum_number_of_learners=maximum_number_of_learners) class TestLearningRate: @@ -47,6 +52,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.learning_rate == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'learning_rate' has to be greater than 0."): - AdaBoost(learning_rate=-1) + @pytest.mark.parametrize("learning_rate", [-1.0, 0.0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: float) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", + ): + AdaBoost(learning_rate=learning_rate) diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py index f8e47ae61..b225c9c78 100644 --- a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import ElasticNetRegression @@ -19,9 +20,13 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.alpha == 1 - def test_should_raise_if_less_than_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'alpha' must be non-negative"): - ElasticNetRegression(alpha=-1) + @pytest.mark.parametrize("alpha", [-0.5], ids=["minus_0_point_5"]) + def test_should_raise_if_less_than_0(self, alpha: float) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"alpha \(={alpha}\) is not inside \[0, \u221e\)\.", + ): + ElasticNetRegression(alpha=alpha) def test_should_warn_if_equal_to_0(self) -> None: with pytest.warns( @@ -44,13 +49,13 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.l1_ratio == 0.3 - def test_should_raise_if_less_than_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'lasso_ratio' must be between 0 and 1."): - ElasticNetRegression(lasso_ratio=-1.0) - - def test_should_raise_if_greater_than_1(self) -> None: - with pytest.raises(ValueError, match="The parameter 'lasso_ratio' must be between 0 and 1."): - ElasticNetRegression(lasso_ratio=2.0) + @pytest.mark.parametrize("lasso_ratio", [-0.5, 1.5], ids=["minus_zero_point_5", "one_point_5"]) + def test_should_raise_if_not_between_0_and_1(self, lasso_ratio: float) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"lasso_ratio \(={lasso_ratio}\) is not inside \[0, 1\]\.", + ): + ElasticNetRegression(lasso_ratio=lasso_ratio) def test_should_warn_if_0(self) -> None: with pytest.warns( diff --git a/tests/safeds/ml/classical/regression/test_gradient_boosting.py b/tests/safeds/ml/classical/regression/test_gradient_boosting.py index bf92e381e..a227e17e1 100644 --- a/tests/safeds/ml/classical/regression/test_gradient_boosting.py +++ b/tests/safeds/ml/classical/regression/test_gradient_boosting.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import GradientBoosting @@ -19,9 +20,13 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.n_estimators == 2 - def test_should_raise_if_less_than_1(self) -> None: - with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."): - GradientBoosting(number_of_trees=0) + @pytest.mark.parametrize("number_of_trees", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_1(self, number_of_trees: int) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"number_of_trees \(={number_of_trees}\) is not inside \(0, \u221e\)\.", + ): + GradientBoosting(number_of_trees=number_of_trees) class TestLearningRate: @@ -34,6 +39,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.learning_rate == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'learning_rate' has to be greater than 0."): - GradientBoosting(learning_rate=-1) + @pytest.mark.parametrize("learning_rate", [-1.0, 0.0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, learning_rate: float) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"learning_rate \(={learning_rate}\) is not inside \(0, \u221e\)\.", + ): + GradientBoosting(learning_rate=learning_rate) diff --git a/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py b/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py index 5450a99f3..8726caff9 100644 --- a/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py +++ b/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import KNearestNeighbors @@ -19,9 +20,13 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.n_neighbors == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'number_of_neighbors' has to be greater than 0."): - KNearestNeighbors(number_of_neighbors=-1) + @pytest.mark.parametrize("number_of_neighbors", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, number_of_neighbors: int) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"number_of_neighbors \(={number_of_neighbors}\) is not inside \(0, \u221e\)\.", + ): + KNearestNeighbors(number_of_neighbors=number_of_neighbors) def test_should_raise_if_greater_than_sample_size(self, training_set: TaggedTable) -> None: with pytest.raises(ValueError, match="has to be less than or equal to the sample size"): diff --git a/tests/safeds/ml/classical/regression/test_lasso_regression.py b/tests/safeds/ml/classical/regression/test_lasso_regression.py index 332b9f1ad..6705aa948 100644 --- a/tests/safeds/ml/classical/regression/test_lasso_regression.py +++ b/tests/safeds/ml/classical/regression/test_lasso_regression.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import LassoRegression @@ -19,9 +20,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.alpha == 1 - def test_should_raise_if_less_than_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'alpha' must be non-negative"): - LassoRegression(alpha=-1) + @pytest.mark.parametrize("alpha", [-0.5], ids=["minus_zero_point_5"]) + def test_should_raise_if_less_than_0(self, alpha: float) -> None: + with pytest.raises(OutOfBoundsError, match=rf"alpha \(={alpha}\) is not inside \[0, \u221e\)\."): + LassoRegression(alpha=alpha) def test_should_warn_if_equal_to_0(self) -> None: with pytest.warns( diff --git a/tests/safeds/ml/classical/regression/test_random_forest.py b/tests/safeds/ml/classical/regression/test_random_forest.py index 89f103704..409f7fca3 100644 --- a/tests/safeds/ml/classical/regression/test_random_forest.py +++ b/tests/safeds/ml/classical/regression/test_random_forest.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import RandomForest @@ -19,6 +20,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.n_estimators == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'number_of_trees' has to be greater than 0."): - RandomForest(number_of_trees=-1) + @pytest.mark.parametrize("number_of_trees", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, number_of_trees: int) -> None: + with pytest.raises( + OutOfBoundsError, + match=rf"number_of_trees \(={number_of_trees}\) is not inside \[1, \u221e\)\.", + ): + RandomForest(number_of_trees=number_of_trees) diff --git a/tests/safeds/ml/classical/regression/test_ridge_regression.py b/tests/safeds/ml/classical/regression/test_ridge_regression.py index ee3feb0b8..954fb6bbf 100644 --- a/tests/safeds/ml/classical/regression/test_ridge_regression.py +++ b/tests/safeds/ml/classical/regression/test_ridge_regression.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import RidgeRegression @@ -19,9 +20,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.alpha == 1 - def test_should_raise_if_less_than_0(self) -> None: - with pytest.raises(ValueError, match="The parameter 'alpha' must be non-negative"): - RidgeRegression(alpha=-1) + @pytest.mark.parametrize("alpha", [-0.5], ids=["minus_zero_point_5"]) + def test_should_raise_if_less_than_0(self, alpha: float) -> None: + with pytest.raises(OutOfBoundsError, match=rf"alpha \(={alpha}\) is not inside \[0, \u221e\)\."): + RidgeRegression(alpha=alpha) def test_should_warn_if_equal_to_0(self) -> None: with pytest.warns( diff --git a/tests/safeds/ml/classical/regression/test_support_vector_machine.py b/tests/safeds/ml/classical/regression/test_support_vector_machine.py index 634c45901..82bd86e81 100644 --- a/tests/safeds/ml/classical/regression/test_support_vector_machine.py +++ b/tests/safeds/ml/classical/regression/test_support_vector_machine.py @@ -1,5 +1,6 @@ import pytest from safeds.data.tabular.containers import Table, TaggedTable +from safeds.exceptions import OutOfBoundsError from safeds.ml.classical.regression import SupportVectorMachine @@ -19,12 +20,10 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: assert fitted_model._wrapped_regressor is not None assert fitted_model._wrapped_regressor.C == 2 - def test_should_raise_if_less_than_or_equal_to_0(self) -> None: - with pytest.raises( - ValueError, - match="The parameter 'c' has to be strictly positive.", - ): - SupportVectorMachine(c=-1) + @pytest.mark.parametrize("c", [-1.0, 0.0], ids=["minus_one", "zero"]) + def test_should_raise_if_less_than_or_equal_to_0(self, c: float) -> None: + with pytest.raises(OutOfBoundsError, match=rf"c \(={c}\) is not inside \(0, \u221e\)\."): + SupportVectorMachine(c=c) class TestKernel: @@ -45,9 +44,10 @@ def test_should_get_sklearn_kernel_linear(self) -> None: linear_kernel = svm.kernel.get_sklearn_kernel() assert linear_kernel == "linear" - def test_should_raise_if_degree_less_than_1(self) -> None: - with pytest.raises(ValueError, match="The parameter 'degree' has to be greater than or equal to 1."): - SupportVectorMachine.Kernel.Polynomial(degree=0) + @pytest.mark.parametrize("degree", [-1, 0], ids=["minus_one", "zero"]) + def test_should_raise_if_degree_less_than_1(self, degree: int) -> None: + with pytest.raises(OutOfBoundsError, match=rf"degree \(={degree}\) is not inside \[1, \u221e\)\."): + SupportVectorMachine.Kernel.Polynomial(degree=degree) def test_should_get_sklearn_kernel_polynomial(self) -> None: svm = SupportVectorMachine(c=2, kernel=SupportVectorMachine.Kernel.Polynomial(degree=2)) From 347f2c546b93e23cc53b673bc0d74255f5471d08 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 22:44:12 +0000 Subject: [PATCH 74/77] style: apply automated linter fixes --- src/safeds/ml/classical/regression/_ada_boost.py | 5 ++--- .../ml/classical/regression/_elastic_net_regression.py | 7 ++----- src/safeds/ml/classical/regression/_gradient_boosting.py | 2 +- src/safeds/ml/classical/regression/_k_nearest_neighbors.py | 2 +- src/safeds/ml/classical/regression/_lasso_regression.py | 2 +- src/safeds/ml/classical/regression/_random_forest.py | 2 +- src/safeds/ml/classical/regression/_ridge_regression.py | 2 +- .../ml/classical/regression/_support_vector_machine.py | 2 +- 8 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/safeds/ml/classical/regression/_ada_boost.py b/src/safeds/ml/classical/regression/_ada_boost.py index f68711676..dfa4a2aec 100644 --- a/src/safeds/ml/classical/regression/_ada_boost.py +++ b/src/safeds/ml/classical/regression/_ada_boost.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from safeds.exceptions import OpenBound, OutOfBoundsError from sklearn.ensemble import AdaBoostRegressor as sk_AdaBoostRegressor +from safeds.exceptions import OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor @@ -46,8 +46,7 @@ def __init__( # Validation if maximum_number_of_learners <= 0: raise OutOfBoundsError( - maximum_number_of_learners, - name="maximum_number_of_learners", lower_bound=OpenBound(0) + maximum_number_of_learners, name="maximum_number_of_learners", lower_bound=OpenBound(0), ) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 3110141cc..ef1e2ad00 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -4,9 +4,9 @@ from typing import TYPE_CHECKING from warnings import warn -from safeds.exceptions import ClosedBound, OutOfBoundsError from sklearn.linear_model import ElasticNet as sk_ElasticNet +from safeds.exceptions import ClosedBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor @@ -49,10 +49,7 @@ def __init__(self, *, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: ) if lasso_ratio < 0 or lasso_ratio > 1: raise OutOfBoundsError( - lasso_ratio, - name="lasso_ratio", - lower_bound=ClosedBound(0), - upper_bound=ClosedBound(1) + lasso_ratio, name="lasso_ratio", lower_bound=ClosedBound(0), upper_bound=ClosedBound(1), ) elif lasso_ratio == 0: warnings.warn( diff --git a/src/safeds/ml/classical/regression/_gradient_boosting.py b/src/safeds/ml/classical/regression/_gradient_boosting.py index 67de10836..376544426 100644 --- a/src/safeds/ml/classical/regression/_gradient_boosting.py +++ b/src/safeds/ml/classical/regression/_gradient_boosting.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, OpenBound from sklearn.ensemble import GradientBoostingRegressor as sk_GradientBoostingRegressor +from safeds.exceptions import OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor diff --git a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py index 37871d334..22104c9ef 100644 --- a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsRegressor as sk_KNeighborsRegressor -from safeds.exceptions import DatasetMissesDataError, OutOfBoundsError, OpenBound +from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor diff --git a/src/safeds/ml/classical/regression/_lasso_regression.py b/src/safeds/ml/classical/regression/_lasso_regression.py index 1c7afb2ee..1826ae9b6 100644 --- a/src/safeds/ml/classical/regression/_lasso_regression.py +++ b/src/safeds/ml/classical/regression/_lasso_regression.py @@ -3,9 +3,9 @@ from typing import TYPE_CHECKING from warnings import warn -from safeds.exceptions import OutOfBoundsError, ClosedBound from sklearn.linear_model import Lasso as sk_Lasso +from safeds.exceptions import ClosedBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor diff --git a/src/safeds/ml/classical/regression/_random_forest.py b/src/safeds/ml/classical/regression/_random_forest.py index 4e14db3cc..a7970ac29 100644 --- a/src/safeds/ml/classical/regression/_random_forest.py +++ b/src/safeds/ml/classical/regression/_random_forest.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, ClosedBound from sklearn.ensemble import RandomForestRegressor as sk_RandomForestRegressor +from safeds.exceptions import ClosedBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor diff --git a/src/safeds/ml/classical/regression/_ridge_regression.py b/src/safeds/ml/classical/regression/_ridge_regression.py index 937101f7f..128551bf3 100644 --- a/src/safeds/ml/classical/regression/_ridge_regression.py +++ b/src/safeds/ml/classical/regression/_ridge_regression.py @@ -3,9 +3,9 @@ import warnings from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, ClosedBound from sklearn.linear_model import Ridge as sk_Ridge +from safeds.exceptions import ClosedBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor diff --git a/src/safeds/ml/classical/regression/_support_vector_machine.py b/src/safeds/ml/classical/regression/_support_vector_machine.py index 0da868402..22b8f0f49 100644 --- a/src/safeds/ml/classical/regression/_support_vector_machine.py +++ b/src/safeds/ml/classical/regression/_support_vector_machine.py @@ -3,9 +3,9 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING -from safeds.exceptions import OutOfBoundsError, OpenBound, ClosedBound from sklearn.svm import SVR as sk_SVR # noqa: N811 +from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from safeds.ml.classical.regression import Regressor From 0616612306f25ac6d40436f6c9ffc418a101a128 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 22:46:13 +0000 Subject: [PATCH 75/77] style: apply automated linter fixes --- src/safeds/ml/classical/regression/_ada_boost.py | 4 +++- .../ml/classical/regression/_elastic_net_regression.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/safeds/ml/classical/regression/_ada_boost.py b/src/safeds/ml/classical/regression/_ada_boost.py index dfa4a2aec..be5fc06e9 100644 --- a/src/safeds/ml/classical/regression/_ada_boost.py +++ b/src/safeds/ml/classical/regression/_ada_boost.py @@ -46,7 +46,9 @@ def __init__( # Validation if maximum_number_of_learners <= 0: raise OutOfBoundsError( - maximum_number_of_learners, name="maximum_number_of_learners", lower_bound=OpenBound(0), + maximum_number_of_learners, + name="maximum_number_of_learners", + lower_bound=OpenBound(0), ) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index ef1e2ad00..00a67d495 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -49,7 +49,10 @@ def __init__(self, *, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: ) if lasso_ratio < 0 or lasso_ratio > 1: raise OutOfBoundsError( - lasso_ratio, name="lasso_ratio", lower_bound=ClosedBound(0), upper_bound=ClosedBound(1), + lasso_ratio, + name="lasso_ratio", + lower_bound=ClosedBound(0), + upper_bound=ClosedBound(1), ) elif lasso_ratio == 0: warnings.warn( From 6b5b15c99d6f24daffa7b3556500f0cdd732bef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Severin=20Paul=20H=C3=B6fer?= <84280965+zzril@users.noreply.github.com> Date: Tue, 11 Jul 2023 01:10:44 +0200 Subject: [PATCH 76/77] Use ClosedBounds for integers --- src/safeds/ml/classical/classification/_ada_boost.py | 8 ++++---- .../ml/classical/classification/_gradient_boosting.py | 6 +++--- .../ml/classical/classification/_k_nearest_neighbors.py | 8 ++++---- src/safeds/ml/classical/classification/_random_forest.py | 6 +++--- src/safeds/ml/classical/regression/_ada_boost.py | 8 ++++---- src/safeds/ml/classical/regression/_gradient_boosting.py | 8 ++++---- .../ml/classical/regression/_k_nearest_neighbors.py | 8 ++++---- src/safeds/ml/classical/regression/_random_forest.py | 2 +- .../safeds/ml/classical/classification/test_ada_boost.py | 2 +- .../ml/classical/classification/test_gradient_boosting.py | 2 +- .../classical/classification/test_k_nearest_neighbors.py | 2 +- .../ml/classical/classification/test_random_forest.py | 2 +- tests/safeds/ml/classical/regression/test_ada_boost.py | 2 +- .../ml/classical/regression/test_gradient_boosting.py | 2 +- .../ml/classical/regression/test_k_nearest_neighbors.py | 2 +- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/safeds/ml/classical/classification/_ada_boost.py b/src/safeds/ml/classical/classification/_ada_boost.py index 299445af3..b2c2746bb 100644 --- a/src/safeds/ml/classical/classification/_ada_boost.py +++ b/src/safeds/ml/classical/classification/_ada_boost.py @@ -4,7 +4,7 @@ from sklearn.ensemble import AdaBoostClassifier as sk_AdaBoostClassifier -from safeds.exceptions import OpenBound, OutOfBoundsError +from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier @@ -33,7 +33,7 @@ class AdaBoost(Classifier): Raises ------ OutOfBoundsError - If `maximum_number_of_learners` or `learning_rate` are less than or equal to 0 + If `maximum_number_of_learners` or `learning_rate` are less than or equal to 0. """ def __init__( @@ -44,11 +44,11 @@ def __init__( learning_rate: float = 1.0, ) -> None: # Validation - if maximum_number_of_learners <= 0: + if maximum_number_of_learners < 1: raise OutOfBoundsError( maximum_number_of_learners, name="maximum_number_of_learners", - lower_bound=OpenBound(0), + lower_bound=ClosedBound(1), ) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/src/safeds/ml/classical/classification/_gradient_boosting.py b/src/safeds/ml/classical/classification/_gradient_boosting.py index 3a646adc9..dc81c0160 100644 --- a/src/safeds/ml/classical/classification/_gradient_boosting.py +++ b/src/safeds/ml/classical/classification/_gradient_boosting.py @@ -4,7 +4,7 @@ from sklearn.ensemble import GradientBoostingClassifier as sk_GradientBoostingClassifier -from safeds.exceptions import OpenBound, OutOfBoundsError +from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier @@ -36,8 +36,8 @@ class GradientBoosting(Classifier): def __init__(self, *, number_of_trees: int = 100, learning_rate: float = 0.1) -> None: # Validation - if number_of_trees <= 0: - raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=OpenBound(0)) + if number_of_trees < 1: + raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=ClosedBound(1)) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py index c90923614..b26dedea5 100644 --- a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsClassifier as sk_KNeighborsClassifier -from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError +from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError, ClosedBound from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier @@ -28,13 +28,13 @@ class KNearestNeighbors(Classifier): Raises ------ OutOfBoundsError - If `number_of_neighbors` is less than or equal to 0. + If `number_of_neighbors` is less than 1. """ def __init__(self, number_of_neighbors: int) -> None: # Validation - if number_of_neighbors <= 0: - raise OutOfBoundsError(number_of_neighbors, name="number_of_neighbors", lower_bound=OpenBound(0)) + if number_of_neighbors < 1: + raise OutOfBoundsError(number_of_neighbors, name="number_of_neighbors", lower_bound=ClosedBound(1)) # Hyperparameters self._number_of_neighbors = number_of_neighbors diff --git a/src/safeds/ml/classical/classification/_random_forest.py b/src/safeds/ml/classical/classification/_random_forest.py index 545ac8f9b..0e7c9b30f 100644 --- a/src/safeds/ml/classical/classification/_random_forest.py +++ b/src/safeds/ml/classical/classification/_random_forest.py @@ -4,7 +4,7 @@ from sklearn.ensemble import RandomForestClassifier as sk_RandomForestClassifier -from safeds.exceptions import OpenBound, OutOfBoundsError +from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier @@ -26,13 +26,13 @@ class RandomForest(Classifier): Raises ------ OutOfBoundsError - If `number_of_trees` is less than or equal to 0. + If `number_of_trees` is less than 1. """ def __init__(self, *, number_of_trees: int = 100) -> None: # Validation if number_of_trees < 1: - raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=OpenBound(0)) + raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=ClosedBound(1)) # Hyperparameters self._number_of_trees = number_of_trees diff --git a/src/safeds/ml/classical/regression/_ada_boost.py b/src/safeds/ml/classical/regression/_ada_boost.py index be5fc06e9..8eb3e44dd 100644 --- a/src/safeds/ml/classical/regression/_ada_boost.py +++ b/src/safeds/ml/classical/regression/_ada_boost.py @@ -4,7 +4,7 @@ from sklearn.ensemble import AdaBoostRegressor as sk_AdaBoostRegressor -from safeds.exceptions import OpenBound, OutOfBoundsError +from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor @@ -33,7 +33,7 @@ class AdaBoost(Regressor): Raises ------ OutOfBoundsError - If `maximum_number_of_learners` or `learning_rate` are less than or equal to 0 + If `maximum_number_of_learners` or `learning_rate` are less than or equal to 0. """ def __init__( @@ -44,11 +44,11 @@ def __init__( learning_rate: float = 1.0, ) -> None: # Validation - if maximum_number_of_learners <= 0: + if maximum_number_of_learners < 1: raise OutOfBoundsError( maximum_number_of_learners, name="maximum_number_of_learners", - lower_bound=OpenBound(0), + lower_bound=ClosedBound(1), ) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/src/safeds/ml/classical/regression/_gradient_boosting.py b/src/safeds/ml/classical/regression/_gradient_boosting.py index 376544426..cc96d1351 100644 --- a/src/safeds/ml/classical/regression/_gradient_boosting.py +++ b/src/safeds/ml/classical/regression/_gradient_boosting.py @@ -4,7 +4,7 @@ from sklearn.ensemble import GradientBoostingRegressor as sk_GradientBoostingRegressor -from safeds.exceptions import OpenBound, OutOfBoundsError +from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor @@ -31,13 +31,13 @@ class GradientBoosting(Regressor): Raises ------ OutOfBoundsError - If `number_of_trees` is less than or equal to 0 or `learning_rate` is non-positive. + If `number_of_trees` or `learning_rate` are less than or equal to 0. """ def __init__(self, *, number_of_trees: int = 100, learning_rate: float = 0.1) -> None: # Validation - if number_of_trees <= 0: - raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=OpenBound(0)) + if number_of_trees < 1: + raise OutOfBoundsError(number_of_trees, name="number_of_trees", lower_bound=ClosedBound(1)) if learning_rate <= 0: raise OutOfBoundsError(learning_rate, name="learning_rate", lower_bound=OpenBound(0)) diff --git a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py index 22104c9ef..f4d569c02 100644 --- a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsRegressor as sk_KNeighborsRegressor -from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError +from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError, ClosedBound from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor @@ -28,13 +28,13 @@ class KNearestNeighbors(Regressor): Raises ------ OutOfBoundsError - If `number_of_neighbors` is less than or equal to 0. + If `number_of_neighbors` is less than 1. """ def __init__(self, number_of_neighbors: int) -> None: # Validation - if number_of_neighbors <= 0: - raise OutOfBoundsError(number_of_neighbors, name="number_of_neighbors", lower_bound=OpenBound(0)) + if number_of_neighbors < 1: + raise OutOfBoundsError(number_of_neighbors, name="number_of_neighbors", lower_bound=ClosedBound(1)) # Hyperparameters self._number_of_neighbors = number_of_neighbors diff --git a/src/safeds/ml/classical/regression/_random_forest.py b/src/safeds/ml/classical/regression/_random_forest.py index a7970ac29..8dd27941d 100644 --- a/src/safeds/ml/classical/regression/_random_forest.py +++ b/src/safeds/ml/classical/regression/_random_forest.py @@ -26,7 +26,7 @@ class RandomForest(Regressor): Raises ------ OutOfBoundsError - If `number_of_trees` is less than or equal to 0. + If `number_of_trees` is less than 1. """ def __init__(self, *, number_of_trees: int = 100) -> None: diff --git a/tests/safeds/ml/classical/classification/test_ada_boost.py b/tests/safeds/ml/classical/classification/test_ada_boost.py index 6bf2017ca..4ed3a339b 100644 --- a/tests/safeds/ml/classical/classification/test_ada_boost.py +++ b/tests/safeds/ml/classical/classification/test_ada_boost.py @@ -37,7 +37,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: def test_should_raise_if_less_than_or_equal_to_0(self, maximum_number_of_learners: int) -> None: with pytest.raises( OutOfBoundsError, - match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \(0, \u221e\)\.", + match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \[1, \u221e\)\.", ): AdaBoost(maximum_number_of_learners=maximum_number_of_learners) diff --git a/tests/safeds/ml/classical/classification/test_gradient_boosting.py b/tests/safeds/ml/classical/classification/test_gradient_boosting.py index be3255871..34fbd2b0b 100644 --- a/tests/safeds/ml/classical/classification/test_gradient_boosting.py +++ b/tests/safeds/ml/classical/classification/test_gradient_boosting.py @@ -24,7 +24,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: def test_should_raise_if_less_than_1(self, number_of_trees: int) -> None: with pytest.raises( OutOfBoundsError, - match=rf"number_of_trees \(={number_of_trees}\) is not inside \(0, \u221e\)\.", + match=rf"number_of_trees \(={number_of_trees}\) is not inside \[1, \u221e\)\.", ): GradientBoosting(number_of_trees=number_of_trees) diff --git a/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py b/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py index 5d8c8aecc..9677d9541 100644 --- a/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py +++ b/tests/safeds/ml/classical/classification/test_k_nearest_neighbors.py @@ -24,7 +24,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: def test_should_raise_if_less_than_or_equal_to_0(self, number_of_neighbors: int) -> None: with pytest.raises( OutOfBoundsError, - match=rf"number_of_neighbors \(={number_of_neighbors}\) is not inside \(0, \u221e\)\.", + match=rf"number_of_neighbors \(={number_of_neighbors}\) is not inside \[1, \u221e\)\.", ): KNearestNeighbors(number_of_neighbors=number_of_neighbors) diff --git a/tests/safeds/ml/classical/classification/test_random_forest.py b/tests/safeds/ml/classical/classification/test_random_forest.py index 852a21554..794a242b9 100644 --- a/tests/safeds/ml/classical/classification/test_random_forest.py +++ b/tests/safeds/ml/classical/classification/test_random_forest.py @@ -24,6 +24,6 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: def test_should_raise_if_less_than_or_equal_to_0(self, number_of_trees: int) -> None: with pytest.raises( OutOfBoundsError, - match=rf"number_of_trees \(={number_of_trees}\) is not inside \(0, \u221e\)\.", + match=rf"number_of_trees \(={number_of_trees}\) is not inside \[1, \u221e\)\.", ): RandomForest(number_of_trees=number_of_trees) diff --git a/tests/safeds/ml/classical/regression/test_ada_boost.py b/tests/safeds/ml/classical/regression/test_ada_boost.py index 0758dcd83..52baec913 100644 --- a/tests/safeds/ml/classical/regression/test_ada_boost.py +++ b/tests/safeds/ml/classical/regression/test_ada_boost.py @@ -37,7 +37,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: def test_should_raise_if_less_than_or_equal_to_0(self, maximum_number_of_learners: int) -> None: with pytest.raises( OutOfBoundsError, - match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \(0, \u221e\)\.", + match=rf"maximum_number_of_learners \(={maximum_number_of_learners}\) is not inside \[1, \u221e\)\.", ): AdaBoost(maximum_number_of_learners=maximum_number_of_learners) diff --git a/tests/safeds/ml/classical/regression/test_gradient_boosting.py b/tests/safeds/ml/classical/regression/test_gradient_boosting.py index a227e17e1..0243551ae 100644 --- a/tests/safeds/ml/classical/regression/test_gradient_boosting.py +++ b/tests/safeds/ml/classical/regression/test_gradient_boosting.py @@ -24,7 +24,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: def test_should_raise_if_less_than_1(self, number_of_trees: int) -> None: with pytest.raises( OutOfBoundsError, - match=rf"number_of_trees \(={number_of_trees}\) is not inside \(0, \u221e\)\.", + match=rf"number_of_trees \(={number_of_trees}\) is not inside \[1, \u221e\)\.", ): GradientBoosting(number_of_trees=number_of_trees) diff --git a/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py b/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py index 8726caff9..69bb27cb4 100644 --- a/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py +++ b/tests/safeds/ml/classical/regression/test_k_nearest_neighbors.py @@ -24,7 +24,7 @@ def test_should_be_passed_to_sklearn(self, training_set: TaggedTable) -> None: def test_should_raise_if_less_than_or_equal_to_0(self, number_of_neighbors: int) -> None: with pytest.raises( OutOfBoundsError, - match=rf"number_of_neighbors \(={number_of_neighbors}\) is not inside \(0, \u221e\)\.", + match=rf"number_of_neighbors \(={number_of_neighbors}\) is not inside \[1, \u221e\)\.", ): KNearestNeighbors(number_of_neighbors=number_of_neighbors) From 5bf1dd6dfe849944eca8f8cbad0569df13071c9d Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Mon, 10 Jul 2023 23:13:04 +0000 Subject: [PATCH 77/77] style: apply automated linter fixes --- src/safeds/ml/classical/classification/_ada_boost.py | 2 +- src/safeds/ml/classical/classification/_gradient_boosting.py | 2 +- src/safeds/ml/classical/classification/_k_nearest_neighbors.py | 2 +- src/safeds/ml/classical/classification/_random_forest.py | 2 +- src/safeds/ml/classical/regression/_ada_boost.py | 2 +- src/safeds/ml/classical/regression/_gradient_boosting.py | 2 +- src/safeds/ml/classical/regression/_k_nearest_neighbors.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/safeds/ml/classical/classification/_ada_boost.py b/src/safeds/ml/classical/classification/_ada_boost.py index b2c2746bb..73b4ef79d 100644 --- a/src/safeds/ml/classical/classification/_ada_boost.py +++ b/src/safeds/ml/classical/classification/_ada_boost.py @@ -4,7 +4,7 @@ from sklearn.ensemble import AdaBoostClassifier as sk_AdaBoostClassifier -from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound +from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier diff --git a/src/safeds/ml/classical/classification/_gradient_boosting.py b/src/safeds/ml/classical/classification/_gradient_boosting.py index dc81c0160..593d1c632 100644 --- a/src/safeds/ml/classical/classification/_gradient_boosting.py +++ b/src/safeds/ml/classical/classification/_gradient_boosting.py @@ -4,7 +4,7 @@ from sklearn.ensemble import GradientBoostingClassifier as sk_GradientBoostingClassifier -from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound +from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier diff --git a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py index b26dedea5..7340af888 100644 --- a/src/safeds/ml/classical/classification/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/classification/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsClassifier as sk_KNeighborsClassifier -from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError, ClosedBound +from safeds.exceptions import ClosedBound, DatasetMissesDataError, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier diff --git a/src/safeds/ml/classical/classification/_random_forest.py b/src/safeds/ml/classical/classification/_random_forest.py index 0e7c9b30f..04e573d5a 100644 --- a/src/safeds/ml/classical/classification/_random_forest.py +++ b/src/safeds/ml/classical/classification/_random_forest.py @@ -4,7 +4,7 @@ from sklearn.ensemble import RandomForestClassifier as sk_RandomForestClassifier -from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound +from safeds.exceptions import ClosedBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._classifier import Classifier diff --git a/src/safeds/ml/classical/regression/_ada_boost.py b/src/safeds/ml/classical/regression/_ada_boost.py index 8eb3e44dd..8403c3479 100644 --- a/src/safeds/ml/classical/regression/_ada_boost.py +++ b/src/safeds/ml/classical/regression/_ada_boost.py @@ -4,7 +4,7 @@ from sklearn.ensemble import AdaBoostRegressor as sk_AdaBoostRegressor -from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound +from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor diff --git a/src/safeds/ml/classical/regression/_gradient_boosting.py b/src/safeds/ml/classical/regression/_gradient_boosting.py index cc96d1351..5d5f3b6ed 100644 --- a/src/safeds/ml/classical/regression/_gradient_boosting.py +++ b/src/safeds/ml/classical/regression/_gradient_boosting.py @@ -4,7 +4,7 @@ from sklearn.ensemble import GradientBoostingRegressor as sk_GradientBoostingRegressor -from safeds.exceptions import OpenBound, OutOfBoundsError, ClosedBound +from safeds.exceptions import ClosedBound, OpenBound, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor diff --git a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py index f4d569c02..fac6dad14 100644 --- a/src/safeds/ml/classical/regression/_k_nearest_neighbors.py +++ b/src/safeds/ml/classical/regression/_k_nearest_neighbors.py @@ -4,7 +4,7 @@ from sklearn.neighbors import KNeighborsRegressor as sk_KNeighborsRegressor -from safeds.exceptions import DatasetMissesDataError, OpenBound, OutOfBoundsError, ClosedBound +from safeds.exceptions import ClosedBound, DatasetMissesDataError, OutOfBoundsError from safeds.ml.classical._util_sklearn import fit, predict from ._regressor import Regressor