From 051b181d18f7774ea5d826c1b85ed6c7e30ffba6 Mon Sep 17 00:00:00 2001 From: Akash Date: Thu, 12 Dec 2024 17:33:19 +0530 Subject: [PATCH 1/4] feat: Implement VersionCompare evaluator for version string comparisons Added Version Compare evaluator --- src/tirith/core/core.py | 5 +- src/tirith/core/evaluators/__init__.py | 2 + src/tirith/core/evaluators/version_compare.py | 79 +++++++++++++++++++ tests/core/evaluators/test_version_compare.py | 45 +++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 src/tirith/core/evaluators/version_compare.py create mode 100644 tests/core/evaluators/test_version_compare.py diff --git a/src/tirith/core/core.py b/src/tirith/core/core.py index 9cb9437..c9112ef 100644 --- a/src/tirith/core/core.py +++ b/src/tirith/core/core.py @@ -33,7 +33,10 @@ def generate_evaluator_result(evaluator_obj, input_data, provider_module): provider_inputs = evaluator_obj.get("provider_args") condition = evaluator_obj.get("condition") evaluator_name: str = condition.get("type") - evaluator_data = condition.get("value") + if evaluator_name is "VersionCompare": + evaluator_data = { "value": condition.get("value"), "operation": condition.get("operation") } + else: + evaluator_data = condition.get("value") evaluator_error_tolerance: int = condition.get("error_tolerance", DEFAULT_ERROR_TOLERANCE) if not condition: diff --git a/src/tirith/core/evaluators/__init__.py b/src/tirith/core/evaluators/__init__.py index 5666af0..ce9ca28 100644 --- a/src/tirith/core/evaluators/__init__.py +++ b/src/tirith/core/evaluators/__init__.py @@ -14,6 +14,7 @@ from .not_equals import NotEquals from .not_contained_in import NotContainedIn from .not_contains import NotContains +from .version_compare import VersionCompare EVALUATORS_DICT: Dict[str, Type[BaseEvaluator]] = { "ContainedIn": ContainedIn, @@ -29,4 +30,5 @@ "NotEquals": NotEquals, "NotContainedIn": NotContainedIn, "NotContains": NotContains, + "VersionCompare": VersionCompare } diff --git a/src/tirith/core/evaluators/version_compare.py b/src/tirith/core/evaluators/version_compare.py new file mode 100644 index 0000000..e88ff43 --- /dev/null +++ b/src/tirith/core/evaluators/version_compare.py @@ -0,0 +1,79 @@ +from .base_evaluator import BaseEvaluator +from packaging.version import Version +import re + + + # Compares two version strings based on the provided operation. + + # Args: + # evaluator_input (str): The first version string to compare. + # evaluator_data (str): The second version string to compare. + # operation (str): The comparison operation to perform. + # Supported values: "LessThan", "LessThanOrEquals", "Equals", "GreaterThan", "GreaterThanOrEquals". + + # Returns: + # dict: A dictionary containing the comparison result (`passed`) and a descriptive message. + + # Example: + # + # >>> comparator = VersionCompare() + # >>> comparator.evaluate("1.0.0", {"value": "1.0.1", "operation": "lessThan"}) + # {'passed': True, 'message': '1.0.0 is less than 1.0.1'} + # >>> comparator.evaluate("1.0.0", {"value": "1.0.0", "operation": "equal"}) + # {'passed': True, 'message': '1.0.0 is equal to 1.0.0'} + # >>> comparator.evaluate("2.0.0", {"value": "1.0.0", "operation": "greaterThan"}) + # {'passed': True, 'message': '2.0.0 is greater than 1.0.0'} + + + # .. versionadded:: 1.0.0 + + + +class VersionCompare(BaseEvaluator): + """Compares two version strings based on the provided operation.""" + + def parse_version(self, version_string): + """Parses a version string and returns a comparable version object.""" + # Extract the version part from the string + match = re.search(r'[\d]+(\.[\d]+)*(\-[a-zA-Z0-9]+)?$', version_string) + if match: + try: + return Version(match.group(0)) + except Exception as e: + raise ValueError(f"Invalid version format: {version_string}. Error: {str(e)}") + else: + raise ValueError(f"Invalid version format: {version_string}") + + def evaluate(self, evaluator_input, evaluator_data): + """Compares two version strings based on the provided operation.""" + evaluation_result = {"passed": False, "message": "Not evaluated"} + try: + v1 = self.parse_version(evaluator_input) + v2 = self.parse_version(evaluator_data['value']) + operation = evaluator_data['operation'] + + if operation == "LessThan": + evaluation_result["passed"] = v1 < v2 + elif operation == "LessThanOrEquals": + evaluation_result["passed"] = v1 <= v2 + elif operation == "Equals": + evaluation_result["passed"] = v1 == v2 + elif operation == "GreaterThan": + evaluation_result["passed"] = v1 > v2 + elif operation == "GreaterThanOrEquals": + evaluation_result["passed"] = v1 >= v2 + + else: + raise ValueError(f"Unsupported operation: {operation}") + + if evaluation_result["passed"]: + evaluation_result["message"] = f"{evaluator_input} is {operation.replace('_', ' ')} {evaluator_data}" + else: + evaluation_result["message"] = f"{evaluator_input} is not {operation.replace('_', ' ')} {evaluator_data}" + + return evaluation_result + + except ValueError as e: + evaluation_result["message"] = str(e) + return evaluation_result + diff --git a/tests/core/evaluators/test_version_compare.py b/tests/core/evaluators/test_version_compare.py new file mode 100644 index 0000000..8ee5f0b --- /dev/null +++ b/tests/core/evaluators/test_version_compare.py @@ -0,0 +1,45 @@ +from tirith.core.evaluators import VersionCompare +from pytest import mark + +# Define test cases +checks_passing = [ + ("1.0.0", {"value": "1.0.1", "operation": "LessThan"}), + ("1.0.0", {"value": "1.0.0", "operation": "Equals"}), + ("2.0.0", {"value": "1.0.0", "operation": "GreaterThan"}), + ("1.0.0", {"value": "1.1.0", "operation": "LessThanOrEquals"}), + ("1.1.0", {"value": "1.1.0", "operation": "LessThanOrEquals"}), + ("1.2.0", {"value": "1.1.0", "operation": "GreaterThanOrEquals"}), + ("1.1.0", {"value": "1.1.0", "operation": "GreaterThanOrEquals"}), +] + +checks_failing = [ + ("1.0.0", {"value": "1.0.1", "operation": "GreaterThan"}), + ("1.0.0", {"value": "1.0.0", "operation": "LessThan"}), + ("1.0.1", {"value": "1.0.0", "operation": "LessThan"}), + ("1.1.0", {"value": "1.0.0", "operation": "Equals"}), +] + + +evaluator = VersionCompare() + +# pytest -v -m passing +@mark.passing +@mark.parametrize("evaluator_input,evaluator_data", checks_passing) +def test_evaluate_passing(evaluator_input, evaluator_data): + result = evaluator.evaluate(evaluator_input, evaluator_data) + operation = evaluator_data["operation"] + assert result == { + "passed": True, + "message": f"{evaluator_input} is {operation} {evaluator_data['value']}" + } + +# pytest -v -m failing +@mark.failing +@mark.parametrize("evaluator_input,evaluator_data", checks_failing) +def test_evaluate_failing(evaluator_input, evaluator_data): + result = evaluator.evaluate(evaluator_input, evaluator_data) + operation = evaluator_data["operation"] + assert result == { + "passed": False, + "message": f"{evaluator_input} is not {operation} {evaluator_data['value']}" + } \ No newline at end of file From fc2d8854b10d71d5c3ff0b0dd4d4293d31734d4f Mon Sep 17 00:00:00 2001 From: Akash Date: Thu, 12 Dec 2024 17:39:35 +0530 Subject: [PATCH 2/4] update test_version_compare.py --- tests/core/evaluators/test_version_compare.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/evaluators/test_version_compare.py b/tests/core/evaluators/test_version_compare.py index 8ee5f0b..6826a91 100644 --- a/tests/core/evaluators/test_version_compare.py +++ b/tests/core/evaluators/test_version_compare.py @@ -30,7 +30,7 @@ def test_evaluate_passing(evaluator_input, evaluator_data): operation = evaluator_data["operation"] assert result == { "passed": True, - "message": f"{evaluator_input} is {operation} {evaluator_data['value']}" + "message": f"{evaluator_input} is {operation} {evaluator_data}" } # pytest -v -m failing @@ -41,5 +41,5 @@ def test_evaluate_failing(evaluator_input, evaluator_data): operation = evaluator_data["operation"] assert result == { "passed": False, - "message": f"{evaluator_input} is not {operation} {evaluator_data['value']}" + "message": f"{evaluator_input} is not {operation} {evaluator_data}" } \ No newline at end of file From bb0dcdbfcfab377aa7c0f3adcfb6871f3e0f0fd8 Mon Sep 17 00:00:00 2001 From: Akash Date: Thu, 12 Dec 2024 17:44:49 +0530 Subject: [PATCH 3/4] fixed linting --- src/tirith/core/core.py | 2 +- src/tirith/core/evaluators/version_compare.py | 48 +++++++++---------- tests/core/evaluators/test_version_compare.py | 12 ++--- 3 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/tirith/core/core.py b/src/tirith/core/core.py index c9112ef..c792ddd 100644 --- a/src/tirith/core/core.py +++ b/src/tirith/core/core.py @@ -34,7 +34,7 @@ def generate_evaluator_result(evaluator_obj, input_data, provider_module): condition = evaluator_obj.get("condition") evaluator_name: str = condition.get("type") if evaluator_name is "VersionCompare": - evaluator_data = { "value": condition.get("value"), "operation": condition.get("operation") } + evaluator_data = {"value": condition.get("value"), "operation": condition.get("operation")} else: evaluator_data = condition.get("value") evaluator_error_tolerance: int = condition.get("error_tolerance", DEFAULT_ERROR_TOLERANCE) diff --git a/src/tirith/core/evaluators/version_compare.py b/src/tirith/core/evaluators/version_compare.py index e88ff43..541f937 100644 --- a/src/tirith/core/evaluators/version_compare.py +++ b/src/tirith/core/evaluators/version_compare.py @@ -3,30 +3,29 @@ import re - # Compares two version strings based on the provided operation. +# Compares two version strings based on the provided operation. - # Args: - # evaluator_input (str): The first version string to compare. - # evaluator_data (str): The second version string to compare. - # operation (str): The comparison operation to perform. - # Supported values: "LessThan", "LessThanOrEquals", "Equals", "GreaterThan", "GreaterThanOrEquals". +# Args: +# evaluator_input (str): The first version string to compare. +# evaluator_data (str): The second version string to compare. +# operation (str): The comparison operation to perform. +# Supported values: "LessThan", "LessThanOrEquals", "Equals", "GreaterThan", "GreaterThanOrEquals". - # Returns: - # dict: A dictionary containing the comparison result (`passed`) and a descriptive message. +# Returns: +# dict: A dictionary containing the comparison result (`passed`) and a descriptive message. - # Example: - # - # >>> comparator = VersionCompare() - # >>> comparator.evaluate("1.0.0", {"value": "1.0.1", "operation": "lessThan"}) - # {'passed': True, 'message': '1.0.0 is less than 1.0.1'} - # >>> comparator.evaluate("1.0.0", {"value": "1.0.0", "operation": "equal"}) - # {'passed': True, 'message': '1.0.0 is equal to 1.0.0'} - # >>> comparator.evaluate("2.0.0", {"value": "1.0.0", "operation": "greaterThan"}) - # {'passed': True, 'message': '2.0.0 is greater than 1.0.0'} +# Example: +# +# >>> comparator = VersionCompare() +# >>> comparator.evaluate("1.0.0", {"value": "1.0.1", "operation": "lessThan"}) +# {'passed': True, 'message': '1.0.0 is less than 1.0.1'} +# >>> comparator.evaluate("1.0.0", {"value": "1.0.0", "operation": "equal"}) +# {'passed': True, 'message': '1.0.0 is equal to 1.0.0'} +# >>> comparator.evaluate("2.0.0", {"value": "1.0.0", "operation": "greaterThan"}) +# {'passed': True, 'message': '2.0.0 is greater than 1.0.0'} - # .. versionadded:: 1.0.0 - +# .. versionadded:: 1.0.0 class VersionCompare(BaseEvaluator): @@ -35,7 +34,7 @@ class VersionCompare(BaseEvaluator): def parse_version(self, version_string): """Parses a version string and returns a comparable version object.""" # Extract the version part from the string - match = re.search(r'[\d]+(\.[\d]+)*(\-[a-zA-Z0-9]+)?$', version_string) + match = re.search(r"[\d]+(\.[\d]+)*(\-[a-zA-Z0-9]+)?$", version_string) if match: try: return Version(match.group(0)) @@ -49,8 +48,8 @@ def evaluate(self, evaluator_input, evaluator_data): evaluation_result = {"passed": False, "message": "Not evaluated"} try: v1 = self.parse_version(evaluator_input) - v2 = self.parse_version(evaluator_data['value']) - operation = evaluator_data['operation'] + v2 = self.parse_version(evaluator_data["value"]) + operation = evaluator_data["operation"] if operation == "LessThan": evaluation_result["passed"] = v1 < v2 @@ -69,11 +68,12 @@ def evaluate(self, evaluator_input, evaluator_data): if evaluation_result["passed"]: evaluation_result["message"] = f"{evaluator_input} is {operation.replace('_', ' ')} {evaluator_data}" else: - evaluation_result["message"] = f"{evaluator_input} is not {operation.replace('_', ' ')} {evaluator_data}" + evaluation_result["message"] = ( + f"{evaluator_input} is not {operation.replace('_', ' ')} {evaluator_data}" + ) return evaluation_result except ValueError as e: evaluation_result["message"] = str(e) return evaluation_result - diff --git a/tests/core/evaluators/test_version_compare.py b/tests/core/evaluators/test_version_compare.py index 6826a91..19982c7 100644 --- a/tests/core/evaluators/test_version_compare.py +++ b/tests/core/evaluators/test_version_compare.py @@ -22,16 +22,15 @@ evaluator = VersionCompare() + # pytest -v -m passing @mark.passing @mark.parametrize("evaluator_input,evaluator_data", checks_passing) def test_evaluate_passing(evaluator_input, evaluator_data): result = evaluator.evaluate(evaluator_input, evaluator_data) operation = evaluator_data["operation"] - assert result == { - "passed": True, - "message": f"{evaluator_input} is {operation} {evaluator_data}" - } + assert result == {"passed": True, "message": f"{evaluator_input} is {operation} {evaluator_data}"} + # pytest -v -m failing @mark.failing @@ -39,7 +38,4 @@ def test_evaluate_passing(evaluator_input, evaluator_data): def test_evaluate_failing(evaluator_input, evaluator_data): result = evaluator.evaluate(evaluator_input, evaluator_data) operation = evaluator_data["operation"] - assert result == { - "passed": False, - "message": f"{evaluator_input} is not {operation} {evaluator_data}" - } \ No newline at end of file + assert result == {"passed": False, "message": f"{evaluator_input} is not {operation} {evaluator_data}"} From fdb6b38a1cf0f0afb72665a8b20b6791850e6797 Mon Sep 17 00:00:00 2001 From: Akash Date: Thu, 12 Dec 2024 17:48:08 +0530 Subject: [PATCH 4/4] fixed linting --- src/tirith/core/evaluators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tirith/core/evaluators/__init__.py b/src/tirith/core/evaluators/__init__.py index ce9ca28..db8b905 100644 --- a/src/tirith/core/evaluators/__init__.py +++ b/src/tirith/core/evaluators/__init__.py @@ -30,5 +30,5 @@ "NotEquals": NotEquals, "NotContainedIn": NotContainedIn, "NotContains": NotContains, - "VersionCompare": VersionCompare + "VersionCompare": VersionCompare, }