From e9ad6726904ad22226479d3dc1983de4e9bc997a Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 2 Feb 2022 18:59:01 +0100 Subject: [PATCH 01/16] add repeatability to condition parser --- .../expressions/ahb_expression_parser.py | 3 +-- .../condition_expression_parser.py | 7 +++---- src/ahbicht/expressions/package_expansion.py | 2 +- unittests/test_ahb_expression_evaluation.py | 2 +- unittests/test_condition_parser.py | 19 ++++++++++++++++++- unittests/test_expression_resolver.py | 2 +- .../test_requirement_constraint_evaluation.py | 2 +- 7 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/ahbicht/expressions/ahb_expression_parser.py b/src/ahbicht/expressions/ahb_expression_parser.py index 3b03771c..f7e0c332 100644 --- a/src/ahbicht/expressions/ahb_expression_parser.py +++ b/src/ahbicht/expressions/ahb_expression_parser.py @@ -32,9 +32,8 @@ def parse_ahb_expression_to_single_requirement_indicator_expressions(ahb_express PREFIX_OPERATOR: "X"i | "O"i | "U"i MODAL_MARK: /M(uss)?|S(oll)?|K(ann)?/i // Matches if it looks like a condition expression, but does not yet check if it is a syntactically valid one: - CONDITION_EXPRESSION: /(?!\BU\B)[\[\]\(\)U∧O∨X⊻\d\sP]+/i + CONDITION_EXPRESSION: /(?!\BU\B)[\[\]\(\)U∧O∨X⊻\d\sP\.]+/i """ - # todo: implement wiederholbarkeiten # Regarding the negative lookahead in the condition expression regex see examples https://regex101.com/r/6fFHD4/1 # and CTRL+F for "Mus[2]" in the unittest that fails if you remove the lookahead. parser = Lark(grammar, start="ahb_expression") diff --git a/src/ahbicht/expressions/condition_expression_parser.py b/src/ahbicht/expressions/condition_expression_parser.py index cb9a178c..a6949d32 100644 --- a/src/ahbicht/expressions/condition_expression_parser.py +++ b/src/ahbicht/expressions/condition_expression_parser.py @@ -38,15 +38,15 @@ def parse_condition_expression_to_tree(condition_expression: str) -> Tree: | package | condition ?brackets: "(" expression ")" - package: "[" PACKAGE_KEY "]" // a rule for packages + package: "[" PACKAGE_KEY REPEATABILITY? "]" // a rule for packages condition: "[" CONDITION_KEY "]" // a rule for condition keys CONDITION_KEY: INT // a TERMINAL for all the remaining ints (lower priority) + REPEATABILITY: /[1-9]\.{2}\d+/ // a terminal for repetitions n..m with n>=0 and m>n PACKAGE_KEY: INT "P" // a TERMINAL for all INTs followed by "P" (high priority) %import common.INT %import common.WS %ignore WS // WS = whitespace """ - # todo: add wiederholbarkeiten https://github.com/Hochfrequenz/ahbicht/issues/96 parser = Lark(grammar, start="expression") try: parsed_tree = parser.parse(condition_expression) @@ -54,13 +54,12 @@ def parse_condition_expression_to_tree(condition_expression: str) -> Tree: raise SyntaxError( """Please make sure that: * all conditions have the form [INT] - * all packages have the form [INTP] + * all packages have the form [INTPn..m] * no conditions are empty * all compositions are combined by operators 'U'/'O'/'X' or without an operator * all open brackets are closed again and vice versa """ ) from eof - # todo: implement wiederholbarkeiten return parsed_tree diff --git a/src/ahbicht/expressions/package_expansion.py b/src/ahbicht/expressions/package_expansion.py index ebed8841..22ea8ded 100644 --- a/src/ahbicht/expressions/package_expansion.py +++ b/src/ahbicht/expressions/package_expansion.py @@ -5,11 +5,11 @@ from abc import ABC, abstractmethod from typing import Mapping, Optional -# pylint:disable=too-few-public-methods from ahbicht.edifact import EdifactFormat, EdifactFormatVersion from ahbicht.mapping_results import PackageKeyConditionExpressionMapping +# pylint:disable=too-few-public-methods class PackageResolver(ABC): """ A package resolver provides condition expressions for given package keys. diff --git a/unittests/test_ahb_expression_evaluation.py b/unittests/test_ahb_expression_evaluation.py index cd18bda2..bd76df12 100644 --- a/unittests/test_ahb_expression_evaluation.py +++ b/unittests/test_ahb_expression_evaluation.py @@ -162,7 +162,7 @@ def side_effect_rc_evaluation(condition_expression): SyntaxError, """Please make sure that: * all conditions have the form [INT] - * all packages have the form [INTP] + * all packages have the form [INTPn..m] * no conditions are empty * all compositions are combined by operators 'U'/'O'/'X' or without an operator * all open brackets are closed again and vice versa diff --git a/unittests/test_condition_parser.py b/unittests/test_condition_parser.py index f6b84d53..c39be5a9 100644 --- a/unittests/test_condition_parser.py +++ b/unittests/test_condition_parser.py @@ -448,6 +448,23 @@ def test_parse_valid_expression_to_tree_with_format_constraints(self, expression ], ), ), + pytest.param( + # nested brackets + "[10P1..5]U([1]O[2])", + Tree( + "and_composition", + [ + Tree(Token("RULE", "package"), [Token("PACKAGE_KEY", "10P"), Token("REPEATABILITY", "1..5")]), + Tree( + "or_composition", + [ + Tree(Token("RULE", "condition"), [Token("CONDITION_KEY", "1")]), + Tree(Token("RULE", "condition"), [Token("CONDITION_KEY", "2")]), + ], + ), + ], + ), + ), ], ) def test_parse_valid_expression_with_brackets_to_tree(self, expression: str, expected_tree: Tree): @@ -483,7 +500,7 @@ def test_parse_invalid_expression(self, expression: str): assert """Please make sure that: * all conditions have the form [INT] - * all packages have the form [INTP] + * all packages have the form [INTPn..m] * no conditions are empty * all compositions are combined by operators 'U'/'O'/'X' or without an operator * all open brackets are closed again and vice versa diff --git a/unittests/test_expression_resolver.py b/unittests/test_expression_resolver.py index e87a151c..de59b762 100644 --- a/unittests/test_expression_resolver.py +++ b/unittests/test_expression_resolver.py @@ -101,7 +101,7 @@ async def test_expression_resolver_failing(self, expression: str): assert """Please make sure that: * all conditions have the form [INT] - * all packages have the form [INTP] + * all packages have the form [INTPn..m] * no conditions are empty * all compositions are combined by operators 'U'/'O'/'X' or without an operator * all open brackets are closed again and vice versa diff --git a/unittests/test_requirement_constraint_evaluation.py b/unittests/test_requirement_constraint_evaluation.py index f0a010a5..c6efea49 100644 --- a/unittests/test_requirement_constraint_evaluation.py +++ b/unittests/test_requirement_constraint_evaluation.py @@ -111,7 +111,7 @@ async def test_evaluate_valid_ahb_expression( SyntaxError, """Please make sure that: * all conditions have the form [INT] - * all packages have the form [INTP] + * all packages have the form [INTPn..m] * no conditions are empty * all compositions are combined by operators 'U'/'O'/'X' or without an operator * all open brackets are closed again and vice versa From 9e2e072f4c170d8d9886e9a6cc2619dbb2b03ad6 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 2 Feb 2022 19:20:35 +0100 Subject: [PATCH 02/16] parse the repeatability in the package rule, handle invalid n..m --- .../condition_expression_parser.py | 2 +- .../expressions/expression_resolver.py | 23 ++++++++++++++++--- unittests/test_package_resolver.py | 20 ++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/ahbicht/expressions/condition_expression_parser.py b/src/ahbicht/expressions/condition_expression_parser.py index a6949d32..4a226942 100644 --- a/src/ahbicht/expressions/condition_expression_parser.py +++ b/src/ahbicht/expressions/condition_expression_parser.py @@ -26,7 +26,7 @@ def parse_condition_expression_to_tree(condition_expression: str) -> Tree: :return parsed_tree: Tree """ - grammar = """ + grammar = r""" ?expression: expression "O"i expression -> or_composition | expression "∨" expression -> or_composition | expression "X"i expression -> xor_composition diff --git a/src/ahbicht/expressions/expression_resolver.py b/src/ahbicht/expressions/expression_resolver.py index c15a3fe5..a63da13d 100644 --- a/src/ahbicht/expressions/expression_resolver.py +++ b/src/ahbicht/expressions/expression_resolver.py @@ -1,11 +1,12 @@ """ This module makes it possible to parse expressions including all their subexpressions, if present. for example ahb_expressions which contain condition_expressions or condition_expressions which contain packages. -Parsing expressions that are nested into other expressions is refered to as "resolving". +Parsing expressions that are nested into other expressions is referred to as "resolving". """ import asyncio import inspect -from typing import Awaitable, List, Union +import re +from typing import Awaitable, List, Match, Union import inject from lark import Token, Transformer, Tree @@ -85,6 +86,9 @@ def CONDITION_EXPRESSION(self, expression): return condition_tree +_repeatability_pattern = re.compile(r"^(?P\d+)\.{2}(?P\d+)$") #: a pattern to match "n..m" repetabilities + + # pylint: disable=no-self-use, invalid-name class PackageExpansionTransformer(Transformer): """ @@ -102,7 +106,20 @@ def package(self, tokens: List[Token]) -> Awaitable[Tree]: return self._package_async(tokens) async def _package_async(self, tokens: List[Token]) -> Tree: - resolved_package = await self._resolver.get_condition_expression(tokens[0].value) + package_key_token = [t for t in tokens if t.type == "PACKAGE_KEY"][0] + repeatability_tokens = [t for t in tokens if t.type == "REPEATABILITY"] + if len(repeatability_tokens) == 1: + # match is not None/Optional[Match] because the grammar already enforced a correct syntax + match: Match[str] = _repeatability_pattern.match(repeatability_tokens[0]) # type:ignore[assignment] + min_repeatability = int(match["min"]) + max_repeatability = int(match["max"]) + if min_repeatability > max_repeatability: + error_message = ( + f"The min repeatability {min_repeatability} must not be greater than the max " + f"repeatability {max_repeatability} " + ) + raise ValueError(error_message) + resolved_package = await self._resolver.get_condition_expression(package_key_token.value) if not resolved_package.has_been_resolved_successfully(): raise NotImplementedError(f"The package '{tokens[0].value}' could not be resolved by {self._resolver}") # the package_expression is not None because that's the definition of "has been resolved successfully" diff --git a/unittests/test_package_resolver.py b/unittests/test_package_resolver.py index a79330f9..25f43ce7 100644 --- a/unittests/test_package_resolver.py +++ b/unittests/test_package_resolver.py @@ -36,6 +36,7 @@ def inject_package_resolver(self, request: SubRequest): "unexpanded_expression, expected_expanded_expression", [ pytest.param("[123P]", "[1] U ([2] O [3])"), + pytest.param("[123P7..8]", "[1] U ([2] O [3])"), pytest.param("[17] U [123P]", "[17] U ([1] U ([2] O [3]))"), ], ) @@ -47,3 +48,22 @@ async def test_correct_injection( assert actual_tree is not None expected_expanded_tree = parse_condition_expression_to_tree(expected_expanded_expression) assert actual_tree == expected_expanded_tree + + @pytest.mark.parametrize( + "inject_package_resolver", + [{"123P": "[1] U ([2] O [3])"}], + indirect=True, + ) + @pytest.mark.parametrize( + "unexpanded_expression, error_message", + [ + pytest.param("[123P8..7]", "The min repeatability 8 must not be greater than the max repeatability 7"), + ], + ) + async def test_invalid_package_repeatability( + self, inject_package_resolver, unexpanded_expression: str, error_message: str + ): + unexpanded_tree = parse_condition_expression_to_tree(unexpanded_expression) + with pytest.raises(ValueError) as invalid_repeatability_error: + _ = await expand_packages(parsed_tree=unexpanded_tree) + assert error_message in str(invalid_repeatability_error) From 451e723b88bd5f777f02acbd2480402cd8cb5e88 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 2 Feb 2022 19:52:56 +0100 Subject: [PATCH 03/16] parse repeatability --- .../expressions/expression_resolver.py | 35 +++++----- src/ahbicht/mapping_results.py | 64 ++++++++++++++++++- unittests/test_utility_functions.py | 28 ++++++++ 3 files changed, 106 insertions(+), 21 deletions(-) diff --git a/src/ahbicht/expressions/expression_resolver.py b/src/ahbicht/expressions/expression_resolver.py index a63da13d..10579b48 100644 --- a/src/ahbicht/expressions/expression_resolver.py +++ b/src/ahbicht/expressions/expression_resolver.py @@ -5,8 +5,7 @@ """ import asyncio import inspect -import re -from typing import Awaitable, List, Match, Union +from typing import Awaitable, List, Optional, Union import inject from lark import Token, Transformer, Tree @@ -15,6 +14,7 @@ from ahbicht.expressions.ahb_expression_parser import parse_ahb_expression_to_single_requirement_indicator_expressions from ahbicht.expressions.condition_expression_parser import parse_condition_expression_to_tree from ahbicht.expressions.package_expansion import PackageResolver +from ahbicht.mapping_results import Repeatability, parse_repeatability async def parse_expression_including_unresolved_subexpressions(expression: str, resolve_packages: bool = False) -> Tree: @@ -86,9 +86,6 @@ def CONDITION_EXPRESSION(self, expression): return condition_tree -_repeatability_pattern = re.compile(r"^(?P\d+)\.{2}(?P\d+)$") #: a pattern to match "n..m" repetabilities - - # pylint: disable=no-self-use, invalid-name class PackageExpansionTransformer(Transformer): """ @@ -103,24 +100,22 @@ def package(self, tokens: List[Token]) -> Awaitable[Tree]: """ try to resolve the package using the injected PackageResolver """ - return self._package_async(tokens) - - async def _package_async(self, tokens: List[Token]) -> Tree: package_key_token = [t for t in tokens if t.type == "PACKAGE_KEY"][0] repeatability_tokens = [t for t in tokens if t.type == "REPEATABILITY"] + repeatability: Optional[Repeatability] if len(repeatability_tokens) == 1: - # match is not None/Optional[Match] because the grammar already enforced a correct syntax - match: Match[str] = _repeatability_pattern.match(repeatability_tokens[0]) # type:ignore[assignment] - min_repeatability = int(match["min"]) - max_repeatability = int(match["max"]) - if min_repeatability > max_repeatability: - error_message = ( - f"The min repeatability {min_repeatability} must not be greater than the max " - f"repeatability {max_repeatability} " - ) - raise ValueError(error_message) + repeatability = parse_repeatability(repeatability_tokens[0].value) + else: + repeatability = None + # todo: what to do with the repeatability? + return self._package_async(package_key_token) + + async def _package_async(self, package_key_token: Token) -> Tree: resolved_package = await self._resolver.get_condition_expression(package_key_token.value) if not resolved_package.has_been_resolved_successfully(): - raise NotImplementedError(f"The package '{tokens[0].value}' could not be resolved by {self._resolver}") + raise NotImplementedError( + f"The package '{package_key_token.value}' could not be resolved by {self._resolver}" + ) # the package_expression is not None because that's the definition of "has been resolved successfully" - return parse_condition_expression_to_tree(resolved_package.package_expression) # type:ignore[arg-type] + tree_result = parse_condition_expression_to_tree(resolved_package.package_expression) # type:ignore[arg-type] + return tree_result diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index 233ee23d..3decdb4e 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -1,7 +1,8 @@ """ This module contains classes that are returned by mappers, meaning they contain a mapping. """ -from typing import Optional +import re +from typing import Match, Optional import attr from marshmallow import Schema, fields, post_load @@ -89,3 +90,64 @@ def deserialize(self, data, **kwargs) -> PackageKeyConditionExpressionMapping: Converts the barely typed data dictionary into an actual :class:`.PackageKeyConditionExpressionMapping` """ return PackageKeyConditionExpressionMapping(**data) + + +# pylint:disable=unused-argument +def check_max_greater_or_equal_than_min(instance: "Repeatability", attribute, value): + """ + assert that max>=min>=0 and max is at least 1 + """ + if not 0 <= instance.min_occurrences < instance.max_occurrences: + raise ValueError(f"0≤n bool: + """ + returns true if the package used together with this repeatability is optional + """ + return self.min_occurrences == 0 + + +_repeatability_pattern = re.compile(r"^(?P\d+)\.{2}(?P\d+)$") #: a pattern to match "n..m" repeatabilities + + +def parse_repeatability(repeatability_string: str) -> Repeatability: + """ + parses the given string as repeatability; f.e. `17..23` is parsed as min=17, max=23 + """ + match: Optional[Match[str]] = _repeatability_pattern.match(repeatability_string) + if match is None: + raise ValueError(f"The given string '{repeatability_string}' could not be parsed as repeatability") + min_repeatability = int(match["min"]) + max_repeatability = int(match["max"]) + if min_repeatability > max_repeatability: + error_message = ( + f"The min repeatability {min_repeatability} must not be greater than the max " + f"repeatability {max_repeatability} " + ) + raise ValueError(error_message) + return Repeatability(min_occurrences=min_repeatability, max_occurrences=max_repeatability) diff --git a/unittests/test_utility_functions.py b/unittests/test_utility_functions.py index 7633d14e..2f441dc0 100644 --- a/unittests/test_utility_functions.py +++ b/unittests/test_utility_functions.py @@ -5,6 +5,7 @@ import pytest # type:ignore[import] +from ahbicht.mapping_results import Repeatability, parse_repeatability from ahbicht.utility_functions import gather_if_necessary pytestmark = pytest.mark.asyncio @@ -36,3 +37,30 @@ class TestUtilityFunctions: async def test_gather_if_necessary(self, mixed_input: List[Union[T, Awaitable[T]]], expected_result: List[T]): actual = await gather_if_necessary(mixed_input) assert actual == expected_result + + @pytest.mark.parametrize( + "candidate, expected_result", + [ + pytest.param("0..1", Repeatability(min_occurrences=0, max_occurrences=1)), + pytest.param("1..2", Repeatability(min_occurrences=1, max_occurrences=2)), + pytest.param("71..89", Repeatability(min_occurrences=71, max_occurrences=89)), + ], + ) + def test_parse_repeatability(self, candidate: str, expected_result: Repeatability): + actual = parse_repeatability(candidate) + assert actual == expected_result + + @pytest.mark.parametrize( + "invalid_candidate", + [ + pytest.param("1..0"), + pytest.param("77..76"), + pytest.param("0..0"), + pytest.param("-1..1"), + pytest.param("bullshit"), + pytest.param(""), + ], + ) + def test_parse_repeatability_failures(self, invalid_candidate): + with pytest.raises(ValueError): + _ = parse_repeatability(invalid_candidate) From 1767df8709f980e2baba33b6086eba0dedf0b1e6 Mon Sep 17 00:00:00 2001 From: konstantin Date: Wed, 2 Feb 2022 19:59:12 +0100 Subject: [PATCH 04/16] more n testcases --- src/ahbicht/expressions/condition_expression_parser.py | 2 +- unittests/test_package_resolver.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ahbicht/expressions/condition_expression_parser.py b/src/ahbicht/expressions/condition_expression_parser.py index 4a226942..47474cb9 100644 --- a/src/ahbicht/expressions/condition_expression_parser.py +++ b/src/ahbicht/expressions/condition_expression_parser.py @@ -41,7 +41,7 @@ def parse_condition_expression_to_tree(condition_expression: str) -> Tree: package: "[" PACKAGE_KEY REPEATABILITY? "]" // a rule for packages condition: "[" CONDITION_KEY "]" // a rule for condition keys CONDITION_KEY: INT // a TERMINAL for all the remaining ints (lower priority) - REPEATABILITY: /[1-9]\.{2}\d+/ // a terminal for repetitions n..m with n>=0 and m>n + REPEATABILITY: /\d+\.{2}[1-9]\d*/ // a terminal for repetitions n..m with n>=0 and m>n PACKAGE_KEY: INT "P" // a TERMINAL for all INTs followed by "P" (high priority) %import common.INT %import common.WS diff --git a/unittests/test_package_resolver.py b/unittests/test_package_resolver.py index 25f43ce7..71072d68 100644 --- a/unittests/test_package_resolver.py +++ b/unittests/test_package_resolver.py @@ -37,6 +37,8 @@ def inject_package_resolver(self, request: SubRequest): [ pytest.param("[123P]", "[1] U ([2] O [3])"), pytest.param("[123P7..8]", "[1] U ([2] O [3])"), + pytest.param("[123P10..11]", "[1] U ([2] O [3])"), + pytest.param("[123P0..5]", "[1] U ([2] O [3])"), pytest.param("[17] U [123P]", "[17] U ([1] U ([2] O [3]))"), ], ) From 9862159537d67ec66aac9a09d32c4cb9c2957490 Mon Sep 17 00:00:00 2001 From: konstantin Date: Fri, 4 Feb 2022 14:39:26 +0100 Subject: [PATCH 05/16] use only 1 point for validation n<=m, no duplicated check --- src/ahbicht/mapping_results.py | 6 ------ unittests/test_package_resolver.py | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index 3decdb4e..f282f2d8 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -144,10 +144,4 @@ def parse_repeatability(repeatability_string: str) -> Repeatability: raise ValueError(f"The given string '{repeatability_string}' could not be parsed as repeatability") min_repeatability = int(match["min"]) max_repeatability = int(match["max"]) - if min_repeatability > max_repeatability: - error_message = ( - f"The min repeatability {min_repeatability} must not be greater than the max " - f"repeatability {max_repeatability} " - ) - raise ValueError(error_message) return Repeatability(min_occurrences=min_repeatability, max_occurrences=max_repeatability) diff --git a/unittests/test_package_resolver.py b/unittests/test_package_resolver.py index 71072d68..fcf2c19c 100644 --- a/unittests/test_package_resolver.py +++ b/unittests/test_package_resolver.py @@ -59,7 +59,7 @@ async def test_correct_injection( @pytest.mark.parametrize( "unexpanded_expression, error_message", [ - pytest.param("[123P8..7]", "The min repeatability 8 must not be greater than the max repeatability 7"), + pytest.param("[123P8..7]", "0≤n Date: Fri, 4 Feb 2022 14:39:44 +0100 Subject: [PATCH 06/16] disable pylint. we're fine with not using the repeatability yet --- src/ahbicht/expressions/expression_resolver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ahbicht/expressions/expression_resolver.py b/src/ahbicht/expressions/expression_resolver.py index 10579b48..3ca1361e 100644 --- a/src/ahbicht/expressions/expression_resolver.py +++ b/src/ahbicht/expressions/expression_resolver.py @@ -102,12 +102,13 @@ def package(self, tokens: List[Token]) -> Awaitable[Tree]: """ package_key_token = [t for t in tokens if t.type == "PACKAGE_KEY"][0] repeatability_tokens = [t for t in tokens if t.type == "REPEATABILITY"] + # pylint: disable=unused-variable + # we parse the repeatability but we don't to anything with it, yet. repeatability: Optional[Repeatability] if len(repeatability_tokens) == 1: repeatability = parse_repeatability(repeatability_tokens[0].value) else: repeatability = None - # todo: what to do with the repeatability? return self._package_async(package_key_token) async def _package_async(self, package_key_token: Token) -> Tree: From f638be2099e2d51ba5dd02a6eed7c848dde1f572 Mon Sep 17 00:00:00 2001 From: konstantin Date: Thu, 10 Feb 2022 09:39:06 +0100 Subject: [PATCH 07/16] use attrs 21.4.0 syntax in Repeatability --- src/ahbicht/expressions/expression_resolver.py | 2 +- src/ahbicht/mapping_results.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ahbicht/expressions/expression_resolver.py b/src/ahbicht/expressions/expression_resolver.py index d1d756ce..4362b9c0 100644 --- a/src/ahbicht/expressions/expression_resolver.py +++ b/src/ahbicht/expressions/expression_resolver.py @@ -113,7 +113,7 @@ def package(self, tokens: List[Token]) -> Awaitable[Tree]: repeatability = None return self._package_async(package_key_token) - async def _package_async(self, package_key_token: Token) -> Tree: + async def _package_async(self, package_key_token: Token) -> Tree[Token]: resolved_package = await self._resolver.get_condition_expression(package_key_token.value) if not resolved_package.has_been_resolved_successfully(): raise NotImplementedError( diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index df1acd0e..bbdac114 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -104,21 +104,21 @@ def check_max_greater_or_equal_than_min(instance: "Repeatability", attribute, va # pylint:disable=too-few-public-methods -@attr.s(auto_attribs=True, kw_only=True) +@attrs.define(auto_attribs=True, kw_only=True) class Repeatability: """ describes how often a segment/code must be used when a "repeatability" is provided with packages """ - min_occurrences: int = attr.ib( - validator=attr.validators.and_(attr.validators.instance_of(int), check_max_greater_or_equal_than_min) + min_occurrences: int = attrs.field( + validator=attrs.validators.and_(attrs.validators.instance_of(int), check_max_greater_or_equal_than_min) ) """ how often the segment/code has to be repeated (lower, inclusive bound); maybe 0 for optional packages """ - max_occurrences: int = attr.ib( - validator=attr.validators.and_(attr.validators.instance_of(int), check_max_greater_or_equal_than_min) + max_occurrences: int = attrs.field( + validator=attrs.validators.and_(attrs.validators.instance_of(int), check_max_greater_or_equal_than_min) ) """ how often the segment/coode may be repeated at most (upper, inclusive bound). From 16a1632ebb897836efbb856ef6f2e9547832bcf0 Mon Sep 17 00:00:00 2001 From: konstantin Date: Fri, 25 Feb 2022 13:04:31 +0100 Subject: [PATCH 08/16] remove condition that is never true --- src/ahbicht/mapping_results.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index bbdac114..5fb5d06b 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -99,8 +99,6 @@ def check_max_greater_or_equal_than_min(instance: "Repeatability", attribute, va """ if not 0 <= instance.min_occurrences < instance.max_occurrences: raise ValueError(f"0≤n Date: Fri, 25 Feb 2022 13:05:46 +0100 Subject: [PATCH 09/16] Update src/ahbicht/mapping_results.py Co-authored-by: Annika <73470827+hf-aschloegl@users.noreply.github.com> --- src/ahbicht/mapping_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index 5fb5d06b..33d99ab3 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -112,7 +112,7 @@ class Repeatability: validator=attrs.validators.and_(attrs.validators.instance_of(int), check_max_greater_or_equal_than_min) ) """ - how often the segment/code has to be repeated (lower, inclusive bound); maybe 0 for optional packages + how often the segment/code has to be repeated (lower, inclusive bound); may be 0 for optional packages """ max_occurrences: int = attrs.field( From b4f35d2d27338890815b779128dfc39e329a6066 Mon Sep 17 00:00:00 2001 From: konstantin Date: Fri, 25 Feb 2022 13:12:13 +0100 Subject: [PATCH 10/16] more comments on token list --- src/ahbicht/expressions/expression_resolver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ahbicht/expressions/expression_resolver.py b/src/ahbicht/expressions/expression_resolver.py index 4362b9c0..65b75b55 100644 --- a/src/ahbicht/expressions/expression_resolver.py +++ b/src/ahbicht/expressions/expression_resolver.py @@ -102,10 +102,13 @@ def package(self, tokens: List[Token]) -> Awaitable[Tree]: """ try to resolve the package using the injected PackageResolver """ + # The grammar guarantees that there is always exactly 1 package_key token/terminal. + # But the repeatability token is optional, so the list repeatability_tokens might contain 0 or 1 entries + # They all come in the same `tokens` list which we split in the following two lines. package_key_token = [t for t in tokens if t.type == "PACKAGE_KEY"][0] repeatability_tokens = [t for t in tokens if t.type == "REPEATABILITY"] # pylint: disable=unused-variable - # we parse the repeatability but we don't to anything with it, yet. + # we parse the repeatability, but we don't to anything with it, yet. repeatability: Optional[Repeatability] if len(repeatability_tokens) == 1: repeatability = parse_repeatability(repeatability_tokens[0].value) From 6fca6e49e238f9503fa267a5633be6ac6ed8f973 Mon Sep 17 00:00:00 2001 From: konstantin Date: Fri, 25 Feb 2022 13:15:53 +0100 Subject: [PATCH 11/16] isort . --- src/ahbicht/expressions/package_expansion.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ahbicht/expressions/package_expansion.py b/src/ahbicht/expressions/package_expansion.py index 8d3a6527..f2ad4fdd 100644 --- a/src/ahbicht/expressions/package_expansion.py +++ b/src/ahbicht/expressions/package_expansion.py @@ -6,6 +6,7 @@ from typing import Mapping, Optional from maus.edifact import EdifactFormat, EdifactFormatVersion + from ahbicht.mapping_results import PackageKeyConditionExpressionMapping From 71d8bfd0c24034af9946eb8da0b2f0514b8115da Mon Sep 17 00:00:00 2001 From: konstantin Date: Fri, 25 Feb 2022 13:33:22 +0100 Subject: [PATCH 12/16] align code and docstring --- src/ahbicht/mapping_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index 7e504d57..9798e998 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -94,7 +94,7 @@ def deserialize(self, data, **kwargs) -> PackageKeyConditionExpressionMapping: # pylint:disable=unused-argument def check_max_greater_or_equal_than_min(instance: "Repeatability", attribute, value): """ - assert that max>=min>=0 and max is at least 1 + assert that 0<=min Date: Fri, 25 Feb 2022 15:19:24 +0100 Subject: [PATCH 13/16] =?UTF-8?q?=E2=9C=85=20(Failing)=20test=20for=20repe?= =?UTF-8?q?atability=201...1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unittests/test_utility_functions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/unittests/test_utility_functions.py b/unittests/test_utility_functions.py index 2f441dc0..a8986225 100644 --- a/unittests/test_utility_functions.py +++ b/unittests/test_utility_functions.py @@ -42,6 +42,7 @@ async def test_gather_if_necessary(self, mixed_input: List[Union[T, Awaitable[T] "candidate, expected_result", [ pytest.param("0..1", Repeatability(min_occurrences=0, max_occurrences=1)), + pytest.param("1..1", Repeatability(min_occurrences=1, max_occurrences=1)), pytest.param("1..2", Repeatability(min_occurrences=1, max_occurrences=2)), pytest.param("71..89", Repeatability(min_occurrences=71, max_occurrences=89)), ], From 941b77c8f13ccfa6639d5682c4f457a2dea4dbbc Mon Sep 17 00:00:00 2001 From: konstantin Date: Fri, 25 Feb 2022 15:57:09 +0100 Subject: [PATCH 14/16] "fix" ;) --- src/ahbicht/mapping_results.py | 6 ++++-- unittests/test_package_resolver.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index 9798e998..51107a48 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -96,8 +96,10 @@ def check_max_greater_or_equal_than_min(instance: "Repeatability", attribute, va """ assert that 0<=min Date: Fri, 25 Feb 2022 15:58:14 +0100 Subject: [PATCH 15/16] better docstring --- src/ahbicht/mapping_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index 51107a48..3030e6cb 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -94,7 +94,7 @@ def deserialize(self, data, **kwargs) -> PackageKeyConditionExpressionMapping: # pylint:disable=unused-argument def check_max_greater_or_equal_than_min(instance: "Repeatability", attribute, value): """ - assert that 0<=min Date: Fri, 25 Feb 2022 15:59:36 +0100 Subject: [PATCH 16/16] pylint --- src/ahbicht/mapping_results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ahbicht/mapping_results.py b/src/ahbicht/mapping_results.py index 3030e6cb..3b755545 100644 --- a/src/ahbicht/mapping_results.py +++ b/src/ahbicht/mapping_results.py @@ -99,7 +99,7 @@ def check_max_greater_or_equal_than_min(instance: "Repeatability", attribute, va if not 0 <= instance.min_occurrences <= instance.max_occurrences: raise ValueError(f"0≤n≤m is not fulfilled for n={instance.min_occurrences}, m={instance.max_occurrences}") if instance.min_occurrences == instance.max_occurrences == 0: - raise ValueError(f"not both min and max occurrences must be 0") + raise ValueError("not both min and max occurrences must be 0") # pylint:disable=too-few-public-methods