|
3 | 3 | from typing import Mapping, Tuple, List, Dict, Any, Union |
4 | 4 | import jmespath |
5 | 5 |
|
| 6 | + |
6 | 7 | from .utils.jmespath_parsers import ( |
7 | 8 | jmespath_value_parser, |
8 | 9 | jmespath_refkey_parser, |
|
13 | 14 | from .utils.data_normalization import exclude_filter, flatten_list |
14 | 15 | from .evaluators import diff_generator, parameter_evaluator, regex_evaluator |
15 | 16 |
|
| 17 | +# pylint: disable=arguments-differ |
| 18 | + |
16 | 19 |
|
17 | 20 | class CheckType: |
18 | 21 | """Check Type Class.""" |
19 | 22 |
|
20 | | - def __init__(self, *args): |
21 | | - """Check Type init method.""" |
22 | | - |
23 | 23 | @staticmethod |
24 | | - def init(*args): |
| 24 | + def init(check_type): |
25 | 25 | """Factory pattern to get the appropriate CheckType implementation. |
26 | 26 |
|
27 | 27 | Args: |
28 | | - *args: Variable length argument list. |
| 28 | + check_type: String to define the type of check. |
29 | 29 | """ |
30 | | - check_type = args[0] |
31 | 30 | if check_type == "exact_match": |
32 | | - return ExactMatchType(*args) |
| 31 | + return ExactMatchType() |
33 | 32 | if check_type == "tolerance": |
34 | | - return ToleranceType(*args) |
| 33 | + return ToleranceType() |
35 | 34 | if check_type == "parameter_match": |
36 | | - return ParameterMatchType(*args) |
| 35 | + return ParameterMatchType() |
37 | 36 | if check_type == "regex": |
38 | | - return RegexType(*args) |
| 37 | + return RegexType() |
39 | 38 |
|
40 | 39 | raise NotImplementedError |
41 | 40 |
|
@@ -89,102 +88,133 @@ def get_value(output: Union[Mapping, List], path: str, exclude: List = None) -> |
89 | 88 |
|
90 | 89 | return values |
91 | 90 |
|
92 | | - def evaluate(self, reference_value: Any, value_to_compare: Any) -> Tuple[Dict, bool]: |
| 91 | + def evaluate(self, value_to_compare: Any, **kwargs) -> Tuple[Dict, bool]: |
93 | 92 | """Return the result of the evaluation and a boolean True if it passes it or False otherwise. |
94 | 93 |
|
95 | 94 | This method is the one that each CheckType has to implement. |
96 | 95 |
|
97 | 96 | Args: |
98 | | - reference_value: Can be any structured data or just a simple value. |
99 | 97 | value_to_compare: Similar value as above to perform comparison. |
100 | 98 |
|
101 | 99 | Returns: |
102 | 100 | tuple: Dictionary representing check result, bool indicating if differences are found. |
103 | 101 | """ |
| 102 | + # This method should call before any other logic the validation of the arguments |
| 103 | + # self.validate(**kwargs) |
| 104 | + raise NotImplementedError |
| 105 | + |
| 106 | + @staticmethod |
| 107 | + def validate(**kwargs): |
| 108 | + """Method to validate arguments that raises proper exceptions.""" |
104 | 109 | raise NotImplementedError |
105 | 110 |
|
106 | 111 |
|
107 | 112 | class ExactMatchType(CheckType): |
108 | 113 | """Exact Match class docstring.""" |
109 | 114 |
|
110 | | - def evaluate(self, reference_value: Any, value_to_compare: Any) -> Tuple[Dict, bool]: |
| 115 | + @staticmethod |
| 116 | + def validate(**kwargs): |
| 117 | + """Method to validate arguments.""" |
| 118 | + # reference_data = getattr(kwargs, "reference_data") |
| 119 | + |
| 120 | + def evaluate(self, value_to_compare: Any, reference_data: Any) -> Tuple[Dict, bool]: |
111 | 121 | """Returns the difference between values and the boolean.""" |
112 | | - evaluation_result = diff_generator(reference_value, value_to_compare) |
| 122 | + self.validate(reference_data=reference_data) |
| 123 | + evaluation_result = diff_generator(reference_data, value_to_compare) |
113 | 124 | return evaluation_result, not evaluation_result |
114 | 125 |
|
115 | 126 |
|
116 | 127 | class ToleranceType(CheckType): |
117 | 128 | """Tolerance class docstring.""" |
118 | 129 |
|
119 | | - def __init__(self, *args): |
120 | | - """Tolerance init method.""" |
121 | | - super().__init__() |
122 | | - |
123 | | - try: |
124 | | - tolerance = float(args[1]) |
125 | | - except IndexError as error: |
126 | | - raise IndexError(f"Tolerance parameter must be defined as float at index 1. You have: {args}") from error |
127 | | - except ValueError as error: |
128 | | - raise ValueError(f"Argument must be convertible to float. You have: {args[1]}") from error |
129 | | - |
130 | | - self.tolerance_factor = tolerance / 100 |
131 | | - |
132 | | - def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple[Dict, bool]: |
| 130 | + @staticmethod |
| 131 | + def validate(**kwargs): |
| 132 | + """Method to validate arguments.""" |
| 133 | + # reference_data = getattr(kwargs, "reference_data") |
| 134 | + tolerance = kwargs.get("tolerance") |
| 135 | + if not tolerance: |
| 136 | + raise ValueError("Tolerance argument is mandatory for Tolerance Check Type.") |
| 137 | + if not isinstance(tolerance, int): |
| 138 | + raise ValueError(f"Tolerance argument must be an integer, and it's {type(tolerance)}.") |
| 139 | + |
| 140 | + def evaluate(self, value_to_compare: Any, reference_data: Any, tolerance: int) -> Tuple[Dict, bool]: |
133 | 141 | """Returns the difference between values and the boolean. Overwrites method in base class.""" |
134 | | - diff = diff_generator(reference_value, value_to_compare) |
135 | | - self._remove_within_tolerance(diff) |
| 142 | + self.validate(reference_data=reference_data, tolerance=tolerance) |
| 143 | + diff = diff_generator(reference_data, value_to_compare) |
| 144 | + self._remove_within_tolerance(diff, tolerance) |
136 | 145 | return diff, not diff |
137 | 146 |
|
138 | | - def _remove_within_tolerance(self, diff: Dict) -> None: |
| 147 | + def _remove_within_tolerance(self, diff: Dict, tolerance: int) -> None: |
139 | 148 | """Recursively look into diff and apply tolerance check, remove reported difference when within tolerance.""" |
140 | 149 |
|
141 | 150 | def _within_tolerance(*, old_value: float, new_value: float) -> bool: |
142 | 151 | """Return True if new value is within the tolerance range of the previous value.""" |
143 | | - max_diff = old_value * self.tolerance_factor |
| 152 | + tolerance_factor = tolerance / 100 |
| 153 | + max_diff = old_value * tolerance_factor |
144 | 154 | return (old_value - max_diff) < new_value < (old_value + max_diff) |
145 | 155 |
|
146 | 156 | for key, value in list(diff.items()): # casting list makes copy, so we don't modify object being iterated. |
147 | 157 | if isinstance(value, dict): |
148 | 158 | if "new_value" in value.keys() and "old_value" in value.keys() and _within_tolerance(**value): |
149 | 159 | diff.pop(key) |
150 | 160 | else: |
151 | | - self._remove_within_tolerance(diff[key]) |
| 161 | + self._remove_within_tolerance(diff[key], tolerance) |
152 | 162 | if not value: |
153 | 163 | diff.pop(key) |
154 | 164 |
|
155 | 165 |
|
156 | 166 | class ParameterMatchType(CheckType): |
157 | 167 | """Parameter Match class implementation.""" |
158 | 168 |
|
159 | | - def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple[Dict, bool]: |
| 169 | + @staticmethod |
| 170 | + def validate(**kwargs): |
| 171 | + """Method to validate arguments.""" |
| 172 | + mode_options = ["match", "no-match"] |
| 173 | + params = kwargs.get("params") |
| 174 | + if not params: |
| 175 | + raise ValueError("Params argument is mandatory for ParameterMatch Check Type.") |
| 176 | + if not isinstance(params, dict): |
| 177 | + raise ValueError(f"Params argument must be a dict, and it's {type(params)}.") |
| 178 | + |
| 179 | + mode = kwargs.get("mode") |
| 180 | + if not mode: |
| 181 | + raise ValueError("Mode argument is mandatory for ParameterMatch Check Type.") |
| 182 | + if not isinstance(mode, str): |
| 183 | + raise ValueError(f"Mode argument must be a string, and it's {type(mode)}.") |
| 184 | + if mode not in mode_options: |
| 185 | + raise ValueError(f"Mode argument should be {mode_options}, and it's {mode}") |
| 186 | + |
| 187 | + def evaluate(self, value_to_compare: Mapping, params: Dict, mode: str) -> Tuple[Dict, bool]: |
160 | 188 | """Parameter Match evaluator implementation.""" |
161 | | - if not isinstance(value_to_compare, dict): |
162 | | - raise TypeError("check_option must be of type dict()") |
163 | | - |
164 | | - evaluation_result = parameter_evaluator(reference_value, value_to_compare) |
| 189 | + self.validate(params=params, mode=mode) |
| 190 | + # TODO: we don't use the mode? |
| 191 | + evaluation_result = parameter_evaluator(value_to_compare, params) |
165 | 192 | return evaluation_result, not evaluation_result |
166 | 193 |
|
167 | 194 |
|
168 | 195 | class RegexType(CheckType): |
169 | 196 | """Regex Match class implementation.""" |
170 | 197 |
|
171 | | - def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple[Mapping, bool]: |
| 198 | + @staticmethod |
| 199 | + def validate(**kwargs): |
| 200 | + """Method to validate arguments.""" |
| 201 | + mode_options = ["match", "no-match"] |
| 202 | + regex = kwargs.get("regex") |
| 203 | + if not regex: |
| 204 | + raise ValueError("Params argument is mandatory for Regex Match Check Type.") |
| 205 | + if not isinstance(regex, str): |
| 206 | + raise ValueError(f"Params argument must be a string, and it's {type(regex)}.") |
| 207 | + |
| 208 | + mode = kwargs.get("mode") |
| 209 | + if not mode: |
| 210 | + raise ValueError("Mode argument is mandatory for Regex Match Check Type.") |
| 211 | + if not isinstance(mode, str): |
| 212 | + raise ValueError(f"Mode argument must be a string, and it's {type(mode)}.") |
| 213 | + if mode not in mode_options: |
| 214 | + raise ValueError(f"Mode argument should be {mode_options}, and it's {mode}") |
| 215 | + |
| 216 | + def evaluate(self, value_to_compare: Mapping, regex: str, mode: str) -> Tuple[Mapping, bool]: |
172 | 217 | """Regex Match evaluator implementation.""" |
173 | | - # Check that check value_to_compare is dict. |
174 | | - if not isinstance(value_to_compare, dict): |
175 | | - raise TypeError("check_option must be of type dict().") |
176 | | - |
177 | | - # Check that value_to_compare has 'regex' and 'mode' dict keys. |
178 | | - if any(key not in value_to_compare.keys() for key in ("regex", "mode")): |
179 | | - raise KeyError( |
180 | | - "Regex check-type requires check-option. Example: dict(regex='.*UNDERLAY.*', mode='no-match')." |
181 | | - ) |
182 | | - |
183 | | - # Assert that check option has 'regex' and 'mode' dict keys.\ |
184 | | - if value_to_compare["mode"] not in ["match", "no-match"]: |
185 | | - raise ValueError( |
186 | | - "Regex check-type requires check-option. Example: dict(regex='.*UNDERLAY.*', mode='no-match')." |
187 | | - ) |
188 | | - |
189 | | - diff = regex_evaluator(reference_value, value_to_compare) |
| 218 | + self.validate(regex=regex, mode=mode) |
| 219 | + diff = regex_evaluator(value_to_compare, regex, mode) |
190 | 220 | return diff, not diff |
0 commit comments