Skip to content

Commit 8b397ba

Browse files
Christian AdellPatryk Szulczewski
andcommitted
Refactor side methods (#27)
* Consolidate extract_values_from_output into CheckType.get_value * Simplify evaluator's file to contain only entrypoints * Small changes to improve comprehension * Small code refactor and comments * rename check_type name and include the main class in package init for easier import * Consolidate utils functions into less files, with some purpose Co-authored-by: Patryk Szulczewski <patryk.szulczewski@networktocode.com>
1 parent b8300ae commit 8b397ba

17 files changed

+486
-481
lines changed

netcompare/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Pre/Post Check library."""
2-
# from .check_type import compare
2+
from .check_types import CheckType
33

44

5-
# __all__ = ["compare"]
5+
__all__ = ["CheckType"]

netcompare/check_type.py renamed to netcompare/check_types.py

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
"""CheckType Implementation."""
2-
from typing import Mapping, Tuple, List, Dict, Any
3-
from .evaluator import diff_generator, parameter_evaluator, regex_evaluator
4-
from .runner import extract_values_from_output
2+
import re
3+
from typing import Mapping, Tuple, List, Dict, Any, Union
4+
import jmespath
5+
6+
from .utils.jmespath_parsers import (
7+
jmespath_value_parser,
8+
jmespath_refkey_parser,
9+
associate_key_of_my_value,
10+
keys_cleaner,
11+
keys_values_zipper,
12+
)
13+
from .utils.data_normalization import exclude_filter, flatten_list
14+
from .evaluators import diff_generator, parameter_evaluator, regex_evaluator
515

616

717
class CheckType:
@@ -26,12 +36,58 @@ def init(*args):
2636
return ParameterMatchType(*args)
2737
if check_type == "regex":
2838
return RegexType(*args)
39+
2940
raise NotImplementedError
3041

3142
@staticmethod
32-
def get_value(output: Mapping, path: str, exclude: List = None) -> Any:
33-
"""Return the value contained into a Mapping for a defined path."""
34-
return extract_values_from_output(output, path, exclude)
43+
def get_value(output: Union[Mapping, List], path: str, exclude: List = None) -> Any:
44+
"""Return data from output depending on the check path. See unit test for complete example.
45+
46+
Get the wanted values to be evaluated if JMESPath expression is defined,
47+
otherwise use the entire output if jmespath is not defined in check. This covers the "raw" diff type.
48+
Exclude data not desired to compare.
49+
50+
Notes:
51+
https://jmespath.org/ shows how JMESPath works.
52+
53+
Args:
54+
output: json data structure
55+
path: JMESPath to extract specific values
56+
exclude: list of keys to exclude
57+
Returns:
58+
Evaluated data, may be anything depending on JMESPath used.
59+
"""
60+
if exclude and isinstance(output, Dict):
61+
exclude_filter(output, exclude) # exclude unwanted elements
62+
63+
if not path:
64+
return output # return if path is not specified
65+
66+
values = jmespath.search(jmespath_value_parser(path), output)
67+
68+
if not any(isinstance(i, list) for i in values): # check for multi-nested lists if not found return here
69+
return values
70+
71+
for element in values: # process elements to check is lists should be flatten
72+
# TODO: Not sure how this is working becasyse from `jmespath.search` it's supposed to get a flat list
73+
# of str or Decimals, not another list...
74+
for item in element:
75+
if isinstance(item, dict): # raise if there is a dict, path must be more specific to extract data
76+
raise TypeError(
77+
f'Must be list of lists i.e. [["Idle", 75759616], ["Idle", 75759620]].' f"You have {values}'."
78+
)
79+
if isinstance(item, list):
80+
values = flatten_list(values) # flatten list and rewrite values
81+
break # items are the same, need to check only first to see if this is a nested list
82+
83+
paired_key_value = associate_key_of_my_value(jmespath_value_parser(path), values)
84+
85+
if re.search(r"\$.*\$", path): # normalize
86+
wanted_reference_keys = jmespath.search(jmespath_refkey_parser(path), output)
87+
list_of_reference_keys = keys_cleaner(wanted_reference_keys)
88+
return keys_values_zipper(list_of_reference_keys, paired_key_value)
89+
90+
return values
3591

3692
def evaluate(self, reference_value: Any, value_to_compare: Any) -> Tuple[Dict, bool]:
3793
"""Return the result of the evaluation and a boolean True if it passes it or False otherwise.
@@ -53,22 +109,22 @@ class ExactMatchType(CheckType):
53109

54110
def evaluate(self, reference_value: Any, value_to_compare: Any) -> Tuple[Dict, bool]:
55111
"""Returns the difference between values and the boolean."""
56-
diff = diff_generator(reference_value, value_to_compare)
57-
return diff, not diff
112+
evaluation_result = diff_generator(reference_value, value_to_compare)
113+
return evaluation_result, not evaluation_result
58114

59115

60116
class ToleranceType(CheckType):
61117
"""Tolerance class docstring."""
62118

63119
def __init__(self, *args):
64120
"""Tolerance init method."""
121+
super().__init__()
122+
65123
try:
66124
tolerance = args[1]
67125
except IndexError as error:
68-
raise f"Tolerance parameter must be defined as float at index 1. You have: {args}" from error
69-
126+
raise ValueError(f"Tolerance parameter must be defined as float at index 1. You have: {args}") from error
70127
self.tolerance_factor = float(tolerance) / 100
71-
super().__init__()
72128

73129
def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple[Dict, bool]:
74130
"""Returns the difference between values and the boolean. Overwrites method in base class."""
@@ -78,20 +134,21 @@ def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple
78134

79135
def _remove_within_tolerance(self, diff: Dict) -> None:
80136
"""Recursively look into diff and apply tolerance check, remove reported difference when within tolerance."""
137+
138+
def _within_tolerance(*, old_value: float, new_value: float) -> bool:
139+
"""Return True if new value is within the tolerance range of the previous value."""
140+
max_diff = old_value * self.tolerance_factor
141+
return (old_value - max_diff) < new_value < (old_value + max_diff)
142+
81143
for key, value in list(diff.items()): # casting list makes copy, so we don't modify object being iterated.
82144
if isinstance(value, dict):
83-
if "new_value" in value.keys() and "old_value" in value.keys() and self._within_tolerance(**value):
145+
if "new_value" in value.keys() and "old_value" in value.keys() and _within_tolerance(**value):
84146
diff.pop(key)
85147
else:
86148
self._remove_within_tolerance(diff[key])
87149
if not value:
88150
diff.pop(key)
89151

90-
def _within_tolerance(self, *, old_value: float, new_value: float) -> bool:
91-
"""Return True if new value is within the tolerance range of the previous value."""
92-
max_diff = old_value * self.tolerance_factor
93-
return (old_value - max_diff) < new_value < (old_value + max_diff)
94-
95152

96153
class ParameterMatchType(CheckType):
97154
"""Parameter Match class implementation."""
@@ -101,12 +158,14 @@ def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple
101158
try:
102159
parameter = value_to_compare[1]
103160
except IndexError as error:
104-
raise f"Evaluating parameter must be defined as dict at index 1. You have: {value_to_compare}" from error
161+
raise ValueError(
162+
f"Evaluating parameter must be defined as dict at index 1. You have: {value_to_compare}"
163+
) from error
105164
if not isinstance(parameter, dict):
106165
raise TypeError("check_option must be of type dict()")
107166

108-
diff = parameter_evaluator(reference_value, parameter)
109-
return diff, not diff
167+
evaluation_result = parameter_evaluator(reference_value, parameter)
168+
return evaluation_result, not evaluation_result
110169

111170

112171
class RegexType(CheckType):
@@ -140,28 +199,3 @@ def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple
140199

141200
diff = regex_evaluator(reference_value, parameter)
142201
return diff, not diff
143-
144-
145-
# TODO: compare is no longer the entry point, we should use the libary as:
146-
# netcompare_check = CheckType.init(check_type_info, options)
147-
# pre_result = netcompare_check.get_value(pre_obj, path)
148-
# post_result = netcompare_check.get_value(post_obj, path)
149-
# netcompare_check.evaluate(pre_result, post_result)
150-
#
151-
# def compare(
152-
# pre_obj: Mapping, post_obj: Mapping, path: Mapping, type_info: Iterable, options: Mapping
153-
# ) -> Tuple[Mapping, bool]:
154-
# """Entry point function.
155-
156-
# Returns a diff object and the boolean of the comparison.
157-
# """
158-
159-
# type_info = type_info.lower()
160-
161-
# try:
162-
# type_obj = CheckType.init(type_info, options)
163-
# except Exception:
164-
# # We will be here if we can't infer the type_obj
165-
# raise
166-
167-
# return type_obj.evaluate(pre_obj, post_obj, path)

netcompare/evaluator.py

Lines changed: 0 additions & 158 deletions
This file was deleted.

0 commit comments

Comments
 (0)