From 3e81b7fbb9090a391468d94df211ef6ed6d2d6af Mon Sep 17 00:00:00 2001 From: Juan Rada-Vilela Date: Mon, 9 Sep 2024 21:05:06 +1000 Subject: [PATCH 1/3] Import Self from fuzzylite to deal with python versions --- fuzzylite/types.py | 8 ++++++- pyproject.toml | 1 + tests/assert_component.py | 12 +++++----- tests/test_defuzzifier.py | 24 +++++++++----------- tests/test_engine.py | 10 ++++---- tests/test_factory.py | 10 ++++---- tests/test_term.py | 48 +++++++++++++++++++-------------------- tests/test_variable.py | 18 +++++++-------- 8 files changed, 68 insertions(+), 63 deletions(-) diff --git a/fuzzylite/types.py b/fuzzylite/types.py index ef96627..15fa0f0 100644 --- a/fuzzylite/types.py +++ b/fuzzylite/types.py @@ -11,8 +11,9 @@ from __future__ import annotations -__all__ = ["Array", "Scalar", "ScalarArray"] +__all__ = ["Array", "Scalar", "ScalarArray", "Self"] +import sys from typing import Any, Union import numpy as np @@ -20,3 +21,8 @@ Scalar = Union[float, np.floating[Any], Array[np.floating[Any]]] ScalarArray = Array[np.floating[Any]] + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self diff --git a/pyproject.toml b/pyproject.toml index 93528db..d584ca0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,6 +109,7 @@ select = [ "ISC", # flake8-implicit-str-concat "N", # pep8 naming "NPY", # NumPy-specific rules + "NPY201", # NumPy 2.0 specific rules # "PD", # pandas-vet "PLW", # warning # "PTH", # flake8-use-pathlib diff --git a/tests/assert_component.py b/tests/assert_component.py index 0d3aef5..66b93d0 100644 --- a/tests/assert_component.py +++ b/tests/assert_component.py @@ -15,9 +15,9 @@ from typing import Any, Generic, TypeVar import numpy as np -from typing_extensions import Self import fuzzylite as fl +from fuzzylite.types import Self T = TypeVar("T") @@ -37,11 +37,11 @@ def __init__(self, test: unittest.TestCase, actual: T) -> None: self.test.maxDiff = None # show all differences def repr_is( - self, - representation: str, - /, - with_alias: str | None = None, - validate: bool = True, + self, + representation: str, + /, + with_alias: str | None = None, + validate: bool = True, ) -> Self: """Asserts that the obtained object's representation is equal to the expected representation.""" diff --git a/tests/test_defuzzifier.py b/tests/test_defuzzifier.py index 8cdd634..c317e79 100644 --- a/tests/test_defuzzifier.py +++ b/tests/test_defuzzifier.py @@ -16,10 +16,9 @@ from typing import Any import numpy as np -from typing_extensions import Self import fuzzylite as fl -from fuzzylite.types import Scalar +from fuzzylite.types import Scalar, Self from tests.assert_component import BaseAssert @@ -44,11 +43,11 @@ def has_attribute(self, **kwargs: Any) -> Self: return self def defuzzifies( - self, - minimum: float, - maximum: float, - terms: dict[fl.Term, float], - vectorized: bool = True, + self, + minimum: float, + maximum: float, + terms: dict[fl.Term, float], + vectorized: bool = True, ) -> Self: """Assert that the defuzzification of the given terms result in the expected values.""" for term, expected in terms.items(): @@ -61,7 +60,6 @@ def defuzzifies( err_msg=f"{fl.Op.class_name(self.actual)}({term}) = {obtained}, but expected {expected}", ) if vectorized: - class StackTerm(fl.Term): def __init__(self, terms: list[fl.Term]) -> None: super().__init__("_") @@ -417,7 +415,7 @@ def test_weighted_defuzzifier(self) -> None: class BaseWeightedDefuzzifier(fl.WeightedDefuzzifier): def defuzzify( - self, term: fl.Term, minimum: float = fl.nan, maximum: float = fl.nan + self, term: fl.Term, minimum: float = fl.nan, maximum: float = fl.nan ) -> Scalar: return fl.nan @@ -528,8 +526,8 @@ def test_weighted_average(self) -> None: defuzzifier = fl.WeightedAverage() with self.assertRaisesRegex( - ValueError, - re.escape("expected an Aggregated term, but found "), + ValueError, + re.escape("expected an Aggregated term, but found "), ): defuzzifier.defuzzify(fl.Triangle()) @@ -630,8 +628,8 @@ def test_weighted_sum(self) -> None: defuzzifier = fl.WeightedSum() with self.assertRaisesRegex( - ValueError, - re.escape("expected an Aggregated term, but found "), + ValueError, + re.escape("expected an Aggregated term, but found "), ): defuzzifier.defuzzify(fl.Triangle()) diff --git a/tests/test_engine.py b/tests/test_engine.py index b5bd98e..3ee5709 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -16,10 +16,10 @@ import black import numpy as np -from typing_extensions import Self import fuzzylite as fl from fuzzylite.examples.mamdani.simple_dimmer import SimpleDimmer +from fuzzylite.types import Self from tests.assert_component import BaseAssert @@ -27,10 +27,10 @@ class EngineAssert(BaseAssert[fl.Engine]): """Engine assert.""" def has_type( - self, - expected: fl.Engine.Type | set[fl.Engine.Type], - /, - reasons: list[str] | None = None, + self, + expected: fl.Engine.Type | set[fl.Engine.Type], + /, + reasons: list[str] | None = None, ) -> Self: """Asserts the engine has the expected type.""" obtained_reasons: list[str] = [] diff --git a/tests/test_factory.py b/tests/test_factory.py index aea3ce9..7e2779d 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -20,9 +20,9 @@ ) import numpy as np -from typing_extensions import Self import fuzzylite as fl +from fuzzylite.types import Self from tests.assert_component import BaseAssert @@ -79,9 +79,9 @@ class FunctionFactoryAssert(BaseAssert[fl.FunctionFactory]): """Function Factory assert.""" def contains_exactly( - self, - elements: set[str], - element_type: fl.Function.Element.Type | None = None, + self, + elements: set[str], + element_type: fl.Function.Element.Type | None = None, ) -> Self: """Assert the factory contains only the expected elements.""" if element_type == fl.Function.Element.Type.Operator: @@ -129,7 +129,7 @@ def precedence_is_higher(self, a: str, b: str) -> Self: elements[a].precedence, elements[b].precedence, msg=f"expected precedence of {a} ({elements[a].precedence}) > {b} ({elements[b].precedence}), " - f"but got {elements[a].precedence} <= {elements[b].precedence}", + f"but got {elements[a].precedence} <= {elements[b].precedence}", ) return self diff --git a/tests/test_term.py b/tests/test_term.py index 9b2df8a..a8b6ae7 100644 --- a/tests/test_term.py +++ b/tests/test_term.py @@ -19,11 +19,11 @@ from typing import Callable, NoReturn import numpy as np -from typing_extensions import Self import fuzzylite as fl import fuzzylite.library from fuzzylite import Scalar, inf, nan +from fuzzylite.types import Self from tests.assert_component import BaseAssert @@ -77,7 +77,7 @@ def configured_as(self, parameters: str) -> Self: return self def has_memberships( - self, x_mf: dict[float, float], heights: Sequence[float] | None = None + self, x_mf: dict[float, float], heights: Sequence[float] | None = None ) -> Self: """Assert the term's membership function produces $f(x{_keys}) = mf_{values}$.""" inputs = fl.scalar(list(x_mf.keys())) @@ -132,10 +132,10 @@ def has_tsukamotos(self, x_mf: dict[float, float]) -> Self: return self def apply( - self, - func: Callable[..., None], - args: Sequence[str] = (), - **keywords: dict[str, object], + self, + func: Callable[..., None], + args: Sequence[str] = (), + **keywords: dict[str, object], ) -> Self: """Applies function on the term with the arguments and keywords as parameters.""" func(self.actual, *args, **keywords) @@ -800,19 +800,19 @@ def test_discrete(self) -> None: term = fl.Discrete() with self.assertRaisesRegex( - ValueError, - re.escape("expected xy to contain coordinate pairs, but it is empty"), + ValueError, + re.escape("expected xy to contain coordinate pairs, but it is empty"), ): term.membership(0.0) with self.assertRaisesRegex( - ValueError, - re.escape("expected xy to contain coordinate pairs, but it is empty"), + ValueError, + re.escape("expected xy to contain coordinate pairs, but it is empty"), ): term.values = fl.Discrete.to_xy([], []) term.membership(0.0) with self.assertRaisesRegex( - ValueError, - re.escape("expected xy to have with 2 columns, but got 1 in shape (1,): [1.]"), + ValueError, + re.escape("expected xy to have with 2 columns, but got 1 in shape (1,): [1.]"), ): term.values = fl.array([1.0]) term.membership(0.0) @@ -1922,7 +1922,7 @@ def value_is(self, expected: str) -> FunctionNodeAssert: return self def evaluates_to( - self, expected: float, variables: dict[str, fl.Scalar] | None = None + self, expected: float, variables: dict[str, fl.Scalar] | None = None ) -> FunctionNodeAssert: """Assert the node evaluates to the expected value (optionally) given variables.""" obtained = self.actual.evaluate(variables) @@ -1979,11 +1979,11 @@ def test_function(self) -> None: output_a = fl.OutputVariable("o_A") engine_a = fl.Engine("A", "Engine A", [input_a], [output_a]) with self.assertRaisesRegex( - ValueError, - re.escape( - "expected a map of variables containing the value for 'i_A', " - "but the map contains: {'x': 0.0}" - ), + ValueError, + re.escape( + "expected a map of variables containing the value for 'i_A', " + "but the map contains: {'x': 0.0}" + ), ): fl.Function.create("engine_a", "2*i_A + o_A + x").membership(0.0) @@ -2010,11 +2010,11 @@ def test_function(self) -> None: function_a.variables = {"x": nan} with self.assertRaisesRegex( - ValueError, - re.escape( - "variable 'x' is reserved for internal use of Function term, " - "please remove it from the map of variables: {'x': nan}" - ), + ValueError, + re.escape( + "variable 'x' is reserved for internal use of Function term, " + "please remove it from the map of variables: {'x': nan}" + ), ): function_a.membership(0.0) del function_a.variables["x"] @@ -2290,7 +2290,7 @@ def test_function_parse(self) -> None: "a+~b": "a b ~ +", "~a*~b": "a ~ b ~ *", "(sin(pi()/4) + cos(pi/4)) / (~sin(pi()/4) - ~cos(pi/4))": "pi 4.000 / sin pi 4.000 / cos + " - "pi 4.000 / sin ~ pi 4.000 / cos ~ - /", + "pi 4.000 / sin ~ pi 4.000 / cos ~ - /", } for infix, postfix in infix_postfix.items(): self.assertEqual(postfix, fl.Function.parse(infix).postfix()) diff --git a/tests/test_variable.py b/tests/test_variable.py index 9e311df..77a107b 100644 --- a/tests/test_variable.py +++ b/tests/test_variable.py @@ -18,9 +18,9 @@ from unittest.mock import MagicMock import numpy as np -from typing_extensions import Self import fuzzylite as fl +from fuzzylite.types import Self from tests.assert_component import BaseAssert T_Variable = TypeVar("T_Variable", bound=fl.Variable) @@ -381,14 +381,14 @@ class TestOutputVariable(unittest.TestCase): """Test the output variable.""" def output_variable( - self, - enabled: bool = True, - name: str = "name", - description: str = "description", - minimum: float = -1.0, - maximum: float = 1.0, - default_value: float = fl.nan, - terms: list[fl.Term] | None = None, + self, + enabled: bool = True, + name: str = "name", + description: str = "description", + minimum: float = -1.0, + maximum: float = 1.0, + default_value: float = fl.nan, + terms: list[fl.Term] | None = None, ) -> fl.OutputVariable: """Create an output variable.""" return fl.OutputVariable( From f7d0882b6c2fb4bf8b326f3a684d2b0d20cd68f9 Mon Sep 17 00:00:00 2001 From: Juan Rada-Vilela Date: Wed, 11 Sep 2024 22:37:20 +1000 Subject: [PATCH 2/3] Remove ruff --- noxfile.py | 4 ++-- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/noxfile.py b/noxfile.py index 1589a84..5501687 100644 --- a/noxfile.py +++ b/noxfile.py @@ -27,7 +27,7 @@ def check(session: nox.Session) -> None: def format(session: nox.Session) -> None: """Run code formatting.""" formatters = { - "ruff": format_ruff, + # "ruff": format_ruff, "black": format_black, } posargs = list(session.posargs) @@ -57,7 +57,7 @@ def format_ruff(session: nox.Session) -> None: def lint(session: nox.Session) -> None: """Run static code analysis and checks format is correct.""" linters = { - "ruff": lint_ruff, + # "ruff": lint_ruff, "black": lint_black, "pyright": lint_pyright, "mypy": lint_mypy, diff --git a/pyproject.toml b/pyproject.toml index d584ca0..6b0f3cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,6 @@ pyright = "^1.1.362" # Static code analysis pytest = "^7.3.1" # Test driven development pytest-benchmark = "^4.0.0" # Local benchmarks pytest-codspeed = "^2.0.1" # Cloud benchmarks -ruff = "^0.4.4" # Code formatting # Packages below are ground truth for version management. See requirements-dev.txt. # poetry = "~=1.8.3" # Build management From b11d7801c54c102265f2c35d2fa856220cfe79bd Mon Sep 17 00:00:00 2001 From: Juan Rada-Vilela Date: Wed, 11 Sep 2024 22:37:56 +1000 Subject: [PATCH 3/3] Black formatting --- tests/assert_component.py | 10 ++++----- tests/test_defuzzifier.py | 21 +++++++++--------- tests/test_engine.py | 8 +++---- tests/test_factory.py | 8 +++---- tests/test_library.py | 2 +- tests/test_term.py | 46 +++++++++++++++++++-------------------- tests/test_variable.py | 16 +++++++------- 7 files changed, 56 insertions(+), 55 deletions(-) diff --git a/tests/assert_component.py b/tests/assert_component.py index 66b93d0..24e3c96 100644 --- a/tests/assert_component.py +++ b/tests/assert_component.py @@ -37,11 +37,11 @@ def __init__(self, test: unittest.TestCase, actual: T) -> None: self.test.maxDiff = None # show all differences def repr_is( - self, - representation: str, - /, - with_alias: str | None = None, - validate: bool = True, + self, + representation: str, + /, + with_alias: str | None = None, + validate: bool = True, ) -> Self: """Asserts that the obtained object's representation is equal to the expected representation.""" diff --git a/tests/test_defuzzifier.py b/tests/test_defuzzifier.py index c317e79..2f09b0f 100644 --- a/tests/test_defuzzifier.py +++ b/tests/test_defuzzifier.py @@ -43,11 +43,11 @@ def has_attribute(self, **kwargs: Any) -> Self: return self def defuzzifies( - self, - minimum: float, - maximum: float, - terms: dict[fl.Term, float], - vectorized: bool = True, + self, + minimum: float, + maximum: float, + terms: dict[fl.Term, float], + vectorized: bool = True, ) -> Self: """Assert that the defuzzification of the given terms result in the expected values.""" for term, expected in terms.items(): @@ -60,6 +60,7 @@ def defuzzifies( err_msg=f"{fl.Op.class_name(self.actual)}({term}) = {obtained}, but expected {expected}", ) if vectorized: + class StackTerm(fl.Term): def __init__(self, terms: list[fl.Term]) -> None: super().__init__("_") @@ -415,7 +416,7 @@ def test_weighted_defuzzifier(self) -> None: class BaseWeightedDefuzzifier(fl.WeightedDefuzzifier): def defuzzify( - self, term: fl.Term, minimum: float = fl.nan, maximum: float = fl.nan + self, term: fl.Term, minimum: float = fl.nan, maximum: float = fl.nan ) -> Scalar: return fl.nan @@ -526,8 +527,8 @@ def test_weighted_average(self) -> None: defuzzifier = fl.WeightedAverage() with self.assertRaisesRegex( - ValueError, - re.escape("expected an Aggregated term, but found "), + ValueError, + re.escape("expected an Aggregated term, but found "), ): defuzzifier.defuzzify(fl.Triangle()) @@ -628,8 +629,8 @@ def test_weighted_sum(self) -> None: defuzzifier = fl.WeightedSum() with self.assertRaisesRegex( - ValueError, - re.escape("expected an Aggregated term, but found "), + ValueError, + re.escape("expected an Aggregated term, but found "), ): defuzzifier.defuzzify(fl.Triangle()) diff --git a/tests/test_engine.py b/tests/test_engine.py index 3ee5709..4445a80 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -27,10 +27,10 @@ class EngineAssert(BaseAssert[fl.Engine]): """Engine assert.""" def has_type( - self, - expected: fl.Engine.Type | set[fl.Engine.Type], - /, - reasons: list[str] | None = None, + self, + expected: fl.Engine.Type | set[fl.Engine.Type], + /, + reasons: list[str] | None = None, ) -> Self: """Asserts the engine has the expected type.""" obtained_reasons: list[str] = [] diff --git a/tests/test_factory.py b/tests/test_factory.py index 7e2779d..879eeb8 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -79,9 +79,9 @@ class FunctionFactoryAssert(BaseAssert[fl.FunctionFactory]): """Function Factory assert.""" def contains_exactly( - self, - elements: set[str], - element_type: fl.Function.Element.Type | None = None, + self, + elements: set[str], + element_type: fl.Function.Element.Type | None = None, ) -> Self: """Assert the factory contains only the expected elements.""" if element_type == fl.Function.Element.Type.Operator: @@ -129,7 +129,7 @@ def precedence_is_higher(self, a: str, b: str) -> Self: elements[a].precedence, elements[b].precedence, msg=f"expected precedence of {a} ({elements[a].precedence}) > {b} ({elements[b].precedence}), " - f"but got {elements[a].precedence} <= {elements[b].precedence}", + f"but got {elements[a].precedence} <= {elements[b].precedence}", ) return self diff --git a/tests/test_library.py b/tests/test_library.py index be55c2a..6554985 100644 --- a/tests/test_library.py +++ b/tests/test_library.py @@ -62,7 +62,7 @@ def test_library_exports_dir(self) -> None: GaussianProduct Linear PiShape Ramp Rectangle SShape SemiEllipse Sigmoid SigmoidDifference SigmoidProduct Spike Term Trapezoid Triangle ZShape -types Array Scalar ScalarArray +types Array Scalar ScalarArray Self variable InputVariable OutputVariable Variable """ diff --git a/tests/test_term.py b/tests/test_term.py index a8b6ae7..c96e08b 100644 --- a/tests/test_term.py +++ b/tests/test_term.py @@ -77,7 +77,7 @@ def configured_as(self, parameters: str) -> Self: return self def has_memberships( - self, x_mf: dict[float, float], heights: Sequence[float] | None = None + self, x_mf: dict[float, float], heights: Sequence[float] | None = None ) -> Self: """Assert the term's membership function produces $f(x{_keys}) = mf_{values}$.""" inputs = fl.scalar(list(x_mf.keys())) @@ -132,10 +132,10 @@ def has_tsukamotos(self, x_mf: dict[float, float]) -> Self: return self def apply( - self, - func: Callable[..., None], - args: Sequence[str] = (), - **keywords: dict[str, object], + self, + func: Callable[..., None], + args: Sequence[str] = (), + **keywords: dict[str, object], ) -> Self: """Applies function on the term with the arguments and keywords as parameters.""" func(self.actual, *args, **keywords) @@ -800,19 +800,19 @@ def test_discrete(self) -> None: term = fl.Discrete() with self.assertRaisesRegex( - ValueError, - re.escape("expected xy to contain coordinate pairs, but it is empty"), + ValueError, + re.escape("expected xy to contain coordinate pairs, but it is empty"), ): term.membership(0.0) with self.assertRaisesRegex( - ValueError, - re.escape("expected xy to contain coordinate pairs, but it is empty"), + ValueError, + re.escape("expected xy to contain coordinate pairs, but it is empty"), ): term.values = fl.Discrete.to_xy([], []) term.membership(0.0) with self.assertRaisesRegex( - ValueError, - re.escape("expected xy to have with 2 columns, but got 1 in shape (1,): [1.]"), + ValueError, + re.escape("expected xy to have with 2 columns, but got 1 in shape (1,): [1.]"), ): term.values = fl.array([1.0]) term.membership(0.0) @@ -1922,7 +1922,7 @@ def value_is(self, expected: str) -> FunctionNodeAssert: return self def evaluates_to( - self, expected: float, variables: dict[str, fl.Scalar] | None = None + self, expected: float, variables: dict[str, fl.Scalar] | None = None ) -> FunctionNodeAssert: """Assert the node evaluates to the expected value (optionally) given variables.""" obtained = self.actual.evaluate(variables) @@ -1979,11 +1979,11 @@ def test_function(self) -> None: output_a = fl.OutputVariable("o_A") engine_a = fl.Engine("A", "Engine A", [input_a], [output_a]) with self.assertRaisesRegex( - ValueError, - re.escape( - "expected a map of variables containing the value for 'i_A', " - "but the map contains: {'x': 0.0}" - ), + ValueError, + re.escape( + "expected a map of variables containing the value for 'i_A', " + "but the map contains: {'x': 0.0}" + ), ): fl.Function.create("engine_a", "2*i_A + o_A + x").membership(0.0) @@ -2010,11 +2010,11 @@ def test_function(self) -> None: function_a.variables = {"x": nan} with self.assertRaisesRegex( - ValueError, - re.escape( - "variable 'x' is reserved for internal use of Function term, " - "please remove it from the map of variables: {'x': nan}" - ), + ValueError, + re.escape( + "variable 'x' is reserved for internal use of Function term, " + "please remove it from the map of variables: {'x': nan}" + ), ): function_a.membership(0.0) del function_a.variables["x"] @@ -2290,7 +2290,7 @@ def test_function_parse(self) -> None: "a+~b": "a b ~ +", "~a*~b": "a ~ b ~ *", "(sin(pi()/4) + cos(pi/4)) / (~sin(pi()/4) - ~cos(pi/4))": "pi 4.000 / sin pi 4.000 / cos + " - "pi 4.000 / sin ~ pi 4.000 / cos ~ - /", + "pi 4.000 / sin ~ pi 4.000 / cos ~ - /", } for infix, postfix in infix_postfix.items(): self.assertEqual(postfix, fl.Function.parse(infix).postfix()) diff --git a/tests/test_variable.py b/tests/test_variable.py index 77a107b..552ce74 100644 --- a/tests/test_variable.py +++ b/tests/test_variable.py @@ -381,14 +381,14 @@ class TestOutputVariable(unittest.TestCase): """Test the output variable.""" def output_variable( - self, - enabled: bool = True, - name: str = "name", - description: str = "description", - minimum: float = -1.0, - maximum: float = 1.0, - default_value: float = fl.nan, - terms: list[fl.Term] | None = None, + self, + enabled: bool = True, + name: str = "name", + description: str = "description", + minimum: float = -1.0, + maximum: float = 1.0, + default_value: float = fl.nan, + terms: list[fl.Term] | None = None, ) -> fl.OutputVariable: """Create an output variable.""" return fl.OutputVariable(