Repository: networktocode/jdiff (language: python)

Issue:
Code review feedback
- [x] Is returning a tuple `(<dict whose structure depends on the check type>, ,<boolean>)` the ideal API for this library? Or would it make sense to define some custom classes/data structures to return, in order make things more user-friendly and more flexible for future expansion?
- [x] Consider removing Python 3.6 support as it's past EOL.
- [x] Is there a reason that CI needs to run `pylint` and `pytest` in containers, but the other linting can run with `INVOKE_LOCAL=True`?
- [x] In pyproject.toml, please disable pylint rules by name rather than by message number, and consider adding comments to explain why rules are disabled - it's not at all obvious for example what rule `E5110` might be or why it's disabled.
- [x] Looks like `tests/unit/` and `tests/integration` directories are just cookiecutter boilerplate - consider deleting them.
- [x] It's not obvious to me why `flatten_list` flattens `[[[[-1, 0], [-1, 0]]]]` to `[[-1, 0], [-1, 0]]` rather than say `[-1, 0, -1, 0]` - consider adding more details to its docstring to make it clearer what it considers to be a "flattened" list and what it doesn't.
- [x] Looks like `exclude_filter` as implemented accepts either a dict or a list for the `data` parameter, but the type annotations say it takes a `Mapping` - perhaps this should be `Union[Dict, List]` instead?
- [x] Personally I find the heavily parameterized tests a bit hard to read and understand (for example, test_diff_generator.py - lots of inputs and expected outputs, very little explanation of *why* these are the expected outputs for any given set of inputs). Not asking for a refactor/redesign here necessarily, just something to consider. Maybe just adding a few comments to each test case might be sufficient.
- [x] For `test_diff_generator` and `test_operators` especially, is there a reason that the pre/post data are stored as separate JSON files, while the other test data are defined inline in the test scripts? Might be more consistent and more maintainable to either store it all as JSON or all as code.
- [x] `test_sw_upgrade_device_state` - would renaming `test_is_passed` to `check_should_pass` be more accurate?
- [x] Is there testing for any of the functions in `netcompare.utils.diff_helpers`? I see TODOs for a couple of them but I may have overlooked testing for the ones that don't have TODOs either.
- [x] Consider adding `mypy` to the linters in order to validate your type hinting is correct. Not blocking.
- [x] The functions in `netcompare.utils.jmespath_parsers` would benefit from more comprehensive docstrings, IMO.
- [x] Is there a reason that `CheckType.get_value()` only supports/uses `exclude` when `output` is a Dict? Given that `exclude_filter` explicitly can handle both dicts and lists? If this is by design, consider raising an exception in the "else" case rather than silently ignoring the `exclude` parameter.
- [x] Looks like `ToleranceType` will [reject `tolerance=0` as invalid](https://github.com/networktocode-llc/netcompare/blob/develop/netcompare/check_types.py#L157) - intentional?
- [x] Is there a reason `tolerance` must be an int, rather than say a float?
- [x] Should `ToleranceType` check that `tolerance` is between 0 and 100? Or are there valid use cases for a >100% tolerance?
- [ ] For required kwargs, consider explicitly declaring them as such in the `validate` signature, e.g., for ToleranceType this would be `def validate(*, tolerance: int)`. Also, is there a reason for the concrete implementations of `validate` to accept wildcard `**kwargs` at all?
- [x] In class `Operator`, `referance_data` should be `reference_data` throughout.
- [x] In class `Operator`, more detailed docstrings would be appreciated.



Discussion / hints:
Thanks for finding the time to look into this. Seems like there is some work to do. I will go through it and let you know once done. 

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6d10547..c1ed5ae 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,6 +19,17 @@ jobs:
         uses: "networktocode/gh-action-setup-poetry-environment@v2"
       - name: "Linting: black"
         run: "poetry run invoke black"
+  mypy:
+    runs-on: "ubuntu-20.04"
+    env:
+      INVOKE_LOCAL: "True"
+    steps:
+      - name: "Check out repository code"
+        uses: "actions/checkout@v2"
+      - name: "Setup environment"
+        uses: "networktocode/gh-action-setup-poetry-environment@v2"
+      - name: "Linting: mypy"
+        run: "poetry run invoke mypy"
   bandit:
     runs-on: "ubuntu-20.04"
     env:
@@ -75,7 +86,7 @@ jobs:
     strategy:
       fail-fast: true
       matrix:
-        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
+        python-version: ["3.7", "3.8", "3.9", "3.10"]
     runs-on: "ubuntu-20.04"
     env:
       PYTHON_VER: "${{ matrix.python-version }}"
@@ -114,6 +125,7 @@ jobs:
         python-version: ["3.7"]
     env:
       PYTHON_VER: "${{ matrix.python-version }}"
+      INVOKE_LOCAL: "True"
     steps:
       - name: "Check out repository code"
         uses: "actions/checkout@v2"
@@ -147,10 +159,11 @@ jobs:
     strategy:
       fail-fast: true
       matrix:
-        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
+        python-version: ["3.7", "3.8", "3.9", "3.10"]
     runs-on: "ubuntu-20.04"
     env:
       PYTHON_VER: "${{ matrix.python-version }}"
+      INVOKE_LOCAL: "True"
     steps:
       - name: "Check out repository code"
         uses: "actions/checkout@v2"
diff --git a/netcompare/check_types.py b/netcompare/check_types.py
index 773bc36..10ea841 100644
--- a/netcompare/check_types.py
+++ b/netcompare/check_types.py
@@ -59,28 +59,30 @@ def get_value(output: Union[Mapping, List], path: str = "*", exclude: List = Non
         if exclude and isinstance(output, Dict):
             if not isinstance(exclude, list):
                 raise ValueError(f"Exclude list must be defined as a list. You have {type(exclude)}")
-
-            exclude_filter(output, exclude)  # exclude unwanted elements
+            # exclude unwanted elements
+            exclude_filter(output, exclude)
 
         if not path:
-            warnings.warn("JMSPath cannot be of type 'None'. Path argumente reverted to default value '*'")
+            warnings.warn("JMSPath cannot be empty string or type 'None'. Path argument reverted to default value '*'")
             path = "*"
 
         if path == "*":
-            return output  # return if path is not specified
+            # return if path is not specified
+            return output
 
         values = jmespath.search(jmespath_value_parser(path), output)
 
-        if not any(isinstance(i, list) for i in values):  # check for multi-nested lists if not found return here
+        # check for multi-nested lists if not found return here
+        if not any(isinstance(i, list) for i in values):
             return values
 
-        for element in values:  # process elements to check is lists should be flatten
-            # TODO: Not sure how this is working because from `jmespath.search` it's supposed to get a flat list
-            # of str or Decimals, not another list...
+        # process elements to check if lists should be flattened
+        for element in values:
             for item in element:
-                if isinstance(item, dict):  # raise if there is a dict, path must be more specific to extract data
+                # raise if there is a dict, path must be more specific to extract data
+                if isinstance(item, dict):
                     raise TypeError(
-                        f'Must be list of lists i.e. [["Idle", 75759616], ["Idle", 75759620]].' f"You have {values}'."
+                        f'Must be list of lists i.e. [["Idle", 75759616], ["Idle", 75759620]]. You have "{values}".'
                     )
                 if isinstance(item, list):
                     values = flatten_list(values)  # flatten list and rewrite values
@@ -123,50 +125,55 @@ def evaluate(self, *args, **kwargs) -> Tuple[Dict, bool]:
             tuple: Dictionary representing check result, bool indicating if differences are found.
         """
         # This method should call before any other logic the validation of the arguments
-        # self.validate(**kwargs)
+        # self._validate(**kwargs)
 
     @staticmethod
     @abstractmethod
-    def validate(**kwargs) -> None:
+    def _validate(*args) -> None:
         """Method to validate arguments that raises proper exceptions."""
 
+    @staticmethod
+    def result(evaluation_result) -> Tuple[Dict, bool]:
+        """Result method implementation. Will return diff data and bool for checking failed result."""
+        return evaluation_result, not evaluation_result
+
 
 class ExactMatchType(CheckType):
     """Exact Match class docstring."""
 
     @staticmethod
-    def validate(**kwargs) -> None:
-        """Method to validate arguments."""
-        # reference_data = getattr(kwargs, "reference_data")
+    def _validate(reference_data):
+        # No need for _validate method as exact-match does not take any specific arguments.
+        pass
 
-    def evaluate(self, value_to_compare: Any, reference_data: Any) -> Tuple[Dict, bool]:
+    def evaluate(self, value_to_compare: Any, reference_data: Any) -> Tuple[Dict, bool]:  # type: ignore[override]
         """Returns the difference between values and the boolean."""
-        self.validate(reference_data=reference_data)
         evaluation_result = diff_generator(reference_data, value_to_compare)
-        return evaluation_result, not evaluation_result
+        return self.result(evaluation_result)
 
 
 class ToleranceType(CheckType):
     """Tolerance class docstring."""
 
     @staticmethod
-    def validate(**kwargs) -> None:
+    def _validate(tolerance) -> None:  # type: ignore[override]
         """Method to validate arguments."""
         # reference_data = getattr(kwargs, "reference_data")
-        tolerance = kwargs.get("tolerance")
         if not tolerance:
             raise ValueError("'tolerance' argument is mandatory for Tolerance Check Type.")
-        if not isinstance(tolerance, int):
-            raise ValueError(f"Tolerance argument's value must be an integer. You have: {type(tolerance)}.")
+        if not isinstance(tolerance, (int, float)):
+            raise ValueError(f"Tolerance argument's value must be a number. You have: {type(tolerance)}.")
+        if tolerance < 0:
+            raise ValueError(f"Tolerance value must be greater than 0. You have: {tolerance}.")
 
-    def evaluate(self, value_to_compare: Any, reference_data: Any, tolerance: int) -> Tuple[Dict, bool]:
+    def evaluate(self, value_to_compare: Any, reference_data: Any, tolerance: int) -> Tuple[Dict, bool]:  # type: ignore[override]
         """Returns the difference between values and the boolean. Overwrites method in base class."""
-        self.validate(reference_data=reference_data, tolerance=tolerance)
-        diff = diff_generator(reference_data, value_to_compare)
-        self._remove_within_tolerance(diff, tolerance)
-        return diff, not diff
+        self._validate(tolerance=tolerance)
+        evaluation_result = diff_generator(reference_data, value_to_compare)
+        self._remove_within_tolerance(evaluation_result, tolerance)
+        return self.result(evaluation_result)
 
-    def _remove_within_tolerance(self, diff: Dict, tolerance: int) -> None:
+    def _remove_within_tolerance(self, diff: Dict, tolerance: Union[int, float]) -> None:
         """Recursively look into diff and apply tolerance check, remove reported difference when within tolerance."""
 
         def _make_float(value: Any) -> float:
@@ -197,16 +204,14 @@ class ParameterMatchType(CheckType):
     """Parameter Match class implementation."""
 
     @staticmethod
-    def validate(**kwargs) -> None:
+    def _validate(params, mode) -> None:  # type: ignore[override]
         """Method to validate arguments."""
         mode_options = ["match", "no-match"]
-        params = kwargs.get("params")
         if not params:
             raise ValueError("'params' argument is mandatory for ParameterMatch Check Type.")
         if not isinstance(params, dict):
             raise ValueError(f"'params' argument must be a dict. You have: {type(params)}.")
 
-        mode = kwargs.get("mode")
         if not mode:
             raise ValueError("'mode' argument is mandatory for ParameterMatch Check Type.")
         if mode not in mode_options:
@@ -214,70 +219,66 @@ def validate(**kwargs) -> None:
                 f"'mode' argument should be one of the following: {', '.join(mode_options)}. You have: {mode}"
             )
 
-    def evaluate(self, value_to_compare: Mapping, params: Dict, mode: str) -> Tuple[Dict, bool]:
+    def evaluate(self, value_to_compare: Mapping, params: Dict, mode: str) -> Tuple[Dict, bool]:  # type: ignore[override]
         """Parameter Match evaluator implementation."""
-        self.validate(params=params, mode=mode)
+        self._validate(params=params, mode=mode)
         # TODO: we don't use the mode?
         evaluation_result = parameter_evaluator(value_to_compare, params, mode)
-        return evaluation_result, not evaluation_result
+        return self.result(evaluation_result)
 
 
 class RegexType(CheckType):
     """Regex Match class implementation."""
 
     @staticmethod
-    def validate(**kwargs) -> None:
+    def _validate(regex, mode) -> None:  # type: ignore[override]
         """Method to validate arguments."""
         mode_options = ["match", "no-match"]
-        regex = kwargs.get("regex")
         if not regex:
             raise ValueError("'regex' argument is mandatory for Regex Check Type.")
         if not isinstance(regex, str):
             raise ValueError(f"'regex' argument must be a string. You have: {type(regex)}.")
 
-        mode = kwargs.get("mode")
         if not mode:
             raise ValueError("'mode' argument is mandatory for Regex Check Type.")
         if mode not in mode_options:
             raise ValueError(f"'mode' argument should be {mode_options}. You have: {mode}")
 
-    def evaluate(self, value_to_compare: Mapping, regex: str, mode: str) -> Tuple[Mapping, bool]:
+    def evaluate(self, value_to_compare: Mapping, regex: str, mode: str) -> Tuple[Dict, bool]:  # type: ignore[override]
         """Regex Match evaluator implementation."""
-        self.validate(regex=regex, mode=mode)
-        diff = regex_evaluator(value_to_compare, regex, mode)
-        return diff, not diff
+        self._validate(regex=regex, mode=mode)
+        evaluation_result = regex_evaluator(value_to_compare, regex, mode)
+        return self.result(evaluation_result)
 
 
 class OperatorType(CheckType):
     """Operator class implementation."""
 
     @staticmethod
-    def validate(**kwargs) -> None:
+    def _validate(params) -> None:  # type: ignore[override]
         """Validate operator parameters."""
         in_operators = ("is-in", "not-in", "in-range", "not-range")
         bool_operators = ("all-same",)
         number_operators = ("is-gt", "is-lt")
-        # "equals" is redundant with check type "exact_match" an "parameter_match"
-        # equal_operators = ("is-equal", "not-equal")
         string_operators = ("contains", "not-contains")
         valid_options = (
             in_operators,
             bool_operators,
             number_operators,
             string_operators,
-            # equal_operators,
         )
 
         # Validate "params" argument is not None.
-        if not kwargs or list(kwargs.keys())[0] != "params":
-            raise ValueError(f"'params' argument must be provided. You have: {list(kwargs.keys())[0]}.")
+        # {'params': {'mode': 'all-same', 'operator_data': True}}
+        if not params or list(params.keys())[0] != "params":
+            raise ValueError(f"'params' argument must be provided. You have: {list(params.keys())[0]}.")
 
-        params_key = kwargs["params"].get("mode")
-        params_value = kwargs["params"].get("operator_data")
+        params_key = params.get("params", {}).get("mode")
+        params_value = params.get("params", {}).get("operator_data")
 
         if not params_key or not params_value:
             raise ValueError(
-                f"'mode' and 'operator_data' arguments must be provided. You have: {list(kwargs['params'].keys())}."
+                f"'mode' and 'operator_data' arguments must be provided. You have: {list(params['params'].keys())}."
             )
 
         # Validate "params" value is legal.
@@ -326,10 +327,18 @@ def validate(**kwargs) -> None:
                 f"check option all-same must have value of type bool. You have: {params_value} of type {type(params_value)}"
             )
 
-    def evaluate(self, value_to_compare: Any, params: Any) -> Tuple[Dict, bool]:
+    def evaluate(self, value_to_compare: Any, params: Any) -> Tuple[Dict, bool]:  # type: ignore[override]
         """Operator evaluator implementation."""
-        self.validate(**params)
+        self._validate(params)
         # For name consistency.
         reference_data = params
-        evaluation_result, evaluation_bool = operator_evaluator(reference_data["params"], value_to_compare)
-        return evaluation_result, not evaluation_bool
+        evaluation_result = operator_evaluator(reference_data["params"], value_to_compare)
+        return self.result(evaluation_result)
+
+    def result(self, evaluation_result):
+        """
+        Operator result method overwrite.
+
+        This is required as Opertor return its own boolean within result.
+        """
+        return evaluation_result[0], not evaluation_result[1]
diff --git a/netcompare/evaluators.py b/netcompare/evaluators.py
index ace9e09..702474f 100644
--- a/netcompare/evaluators.py
+++ b/netcompare/evaluators.py
@@ -104,10 +104,10 @@ def regex_evaluator(values: Mapping, regex_expression: str, mode: str) -> Dict:
     return result
 
 
-def operator_evaluator(referance_data: Mapping, value_to_compare: Mapping) -> Tuple[Dict, bool]:
+def operator_evaluator(reference_data: Mapping, value_to_compare: Mapping) -> Tuple[Dict, bool]:
     """Operator evaluator call."""
-    # referance_data
+    # reference_data
     # {'mode': 'all-same', 'operator_data': True}
-    operator_mode = referance_data["mode"].replace("-", "_")
-    operator = Operator(referance_data["operator_data"], value_to_compare)
+    operator_mode = reference_data["mode"].replace("-", "_")
+    operator = Operator(reference_data["operator_data"], value_to_compare)
     return getattr(operator, operator_mode)()
diff --git a/netcompare/operator.py b/netcompare/operator.py
index e57f4c5..e344305 100644
--- a/netcompare/operator.py
+++ b/netcompare/operator.py
@@ -6,17 +6,49 @@
 class Operator:
     """Operator class implementation."""
 
-    def __init__(self, referance_data: Any, value_to_compare: Any) -> None:
-        """__init__ method."""
+    def __init__(self, reference_data: Any, value_to_compare: Any) -> None:
+        """__init__ method for Operator class."""
         # [{'7.7.7.7': {'peerGroup': 'EVPN-OVERLAY-SPINE', 'vrf': 'default', 'state': 'Idle'}},
         # {'10.1.0.0': {'peerGroup': 'IPv4-UNDERLAY-SPINE', 'vrf': 'default', 'state': 'Idle'}},
         # {'10.2.0.0': {'peerGroup': 'IPv4-UNDERLAY-SPINE', 'vrf': 'default', 'state': 'Idle'}},
         # {'10.64.207.255': {'peerGroup': 'IPv4-UNDERLAY-MLAG-PEER', 'vrf': 'default', 'state': 'Idle'}}]
-        self.referance_data = referance_data
+        self.reference_data = reference_data
         self.value_to_compare = value_to_compare
 
-    def _loop_through_wrapper(self, call_ops: str) -> Tuple[bool, List]:
-        """Wrapper method for operator evaluation."""
+    def _loop_through_wrapper(self, call_ops: str) -> Tuple[List, bool]:
+        """Private wrapper method for operator evaluation based on 'operator' lib.
+
+        Based on value passed to the method, the appropriate operator logic is triggered.
+
+        Args:
+            call_ops: operator type parameter.
+
+        Returns:
+            dict: result evaluation based on operator type.
+        """
+
+        def call_evaluation_logic():
+            """Operator valuation logic wrapper."""
+            # reverse operands: https://docs.python.org/3.8/library/operator.html#operator.contains
+            if call_ops == "is_in":
+                if ops[call_ops](self.reference_data, evaluated_value):
+                    result.append(item)
+            elif call_ops == "not_contains":
+                if not ops[call_ops](evaluated_value, self.reference_data):
+                    result.append(item)
+            elif call_ops == "not_in":
+                if not ops[call_ops](self.reference_data, evaluated_value):
+                    result.append(item)
+            elif call_ops == "in_range":
+                if self.reference_data[0] < evaluated_value < self.reference_data[1]:
+                    result.append(item)
+            elif call_ops == "not_range":
+                if not self.reference_data[0] < evaluated_value < self.reference_data[1]:
+                    result.append(item)
+            # "<", ">", "contains"
+            elif ops[call_ops](evaluated_value, self.reference_data):
+                result.append(item)
+
         ops = {
             ">": operator.gt,
             "<": operator.lt,
@@ -26,85 +58,65 @@ def _loop_through_wrapper(self, call_ops: str) -> Tuple[bool, List]:
             "not_contains": operator.contains,
         }
 
-        result = []
+        result = []  # type: List
         for item in self.value_to_compare:
             for value in item.values():
                 for evaluated_value in value.values():
-                    # reverse operands (??? WHY ???) https://docs.python.org/3.8/library/operator.html#operator.contains
-                    if call_ops == "is_in":
-                        if ops[call_ops](self.referance_data, evaluated_value):
-                            result.append(item)
-                    elif call_ops == "not_contains":
-                        if not ops[call_ops](evaluated_value, self.referance_data):
-                            result.append(item)
-                    elif call_ops == "not_in":
-                        if not ops[call_ops](self.referance_data, evaluated_value):
-                            result.append(item)
-                    elif call_ops == "in_range":
-                        if self.referance_data[0] < evaluated_value < self.referance_data[1]:
-                            result.append(item)
-                    elif call_ops == "not_range":
-                        if not self.referance_data[0] < evaluated_value < self.referance_data[1]:
-                            result.append(item)
-                    # "<", ">", "contains"
-                    elif ops[call_ops](evaluated_value, self.referance_data):
-                        result.append(item)
+                    call_evaluation_logic()
         if result:
             return (result, True)
         return (result, False)
 
     def all_same(self) -> Tuple[bool, Any]:
-        """All same operator implementation."""
+        """All same operator type implementation."""
         list_of_values = []
         result = []
 
         for item in self.value_to_compare:
-            for value in item.values():
-                # Create a list for compare values.
-                list_of_values.append(value)
-
+            # Create a list for compare values.
+            list_of_values.extend(iter(item.values()))
         for element in list_of_values:
             if element != list_of_values[0]:
                 result.append(False)
             else:
                 result.append(True)
 
-        if self.referance_data and not all(result):
+        if self.reference_data and not all(result):
             return (self.value_to_compare, False)
-        if self.referance_data:
+        if self.reference_data:
             return (self.value_to_compare, True)
         if not all(result):
             return (self.value_to_compare, True)
         return (self.value_to_compare, False)
 
-    def contains(self) -> Tuple[bool, List]:
-        """Contains operator implementation."""
+    def contains(self) -> Tuple[List, bool]:
+        """Contains operator caller."""
         return self._loop_through_wrapper("contains")
 
-    def not_contains(self) -> Tuple[bool, List]:
-        """Not contains operator implementation."""
+    def not_contains(self) -> Tuple[List, bool]:
+        """Not contains operator caller."""
         return self._loop_through_wrapper("not_contains")
 
-    def is_gt(self) -> Tuple[bool, List]:
-        """Is greather than operator implementation."""
+    def is_gt(self) -> Tuple[List, bool]:
+        """Is greather than operator caller."""
         return self._loop_through_wrapper(">")
 
-    def is_lt(self) -> Tuple[bool, List]:
-        """Is lower than operator implementation."""
+    def is_lt(self) -> Tuple[List, bool]:
+        """Is lower than operator caller."""
         return self._loop_through_wrapper("<")
 
-    def is_in(self) -> Tuple[bool, List]:
-        """Is in operator implementation."""
+    def is_in(self) -> Tuple[List, bool]:
+        """Is in operator caller."""
         return self._loop_through_wrapper("is_in")
 
-    def not_in(self) -> Tuple[bool, List]:
-        """Is not in operator implementation."""
+    def not_in(self) -> Tuple[List, bool]:
+        """Is not in operator caller."""
         return self._loop_through_wrapper("not_in")
 
-    def in_range(self) -> Tuple[bool, List]:
-        """Is in range operator implementation."""
+    def in_range(self) -> Tuple[List, bool]:
+        """Is in range operator caller."""
         return self._loop_through_wrapper("in_range")
 
-    def not_range(self) -> Tuple[bool, List]:
-        """Is not in range operator implementation."""
+    def not_range(self) -> Tuple[List, bool]:
+        """Is not in range operator caller."""
         return self._loop_through_wrapper("not_range")
diff --git a/netcompare/utils/data_normalization.py b/netcompare/utils/data_normalization.py
index 1fd2fea..8a5e053 100644
--- a/netcompare/utils/data_normalization.py
+++ b/netcompare/utils/data_normalization.py
@@ -1,11 +1,17 @@
 """Data Normalization utilities."""
-from typing import List, Generator, Mapping
+from typing import List, Generator, Union, Dict
 
 
 def flatten_list(my_list: List) -> List:
     """
     Flatten a multi level nested list and returns a list of lists.
 
+    This normalization step is required since jmespath can return nested lists containing the
+    wanted value. This depends how much nested is the wanted value in the json output.
+
+    Having a list of lists will help us to assert that we have the number of values we have, will
+    match the number of reference keys found in json object.
+
     Args:
         my_list: nested list to be flattened.
 
@@ -37,7 +43,7 @@ def is_flat_list(obj: List) -> bool:
     return list(iter_flatten_list(my_list))
 
 
-def exclude_filter(data: Mapping, exclude: List):
+def exclude_filter(data: Union[Dict, List], exclude: List):
     """
     Recusively look through all dict keys and pop out the one defined in "exclude".
 
diff --git a/netcompare/utils/diff_helpers.py b/netcompare/utils/diff_helpers.py
index 9faaefd..fd1b70f 100644
--- a/netcompare/utils/diff_helpers.py
+++ b/netcompare/utils/diff_helpers.py
@@ -2,12 +2,12 @@
 import re
 from collections import defaultdict
 from functools import partial
-from typing import Mapping, Dict, List
+from typing import Mapping, Dict, List, DefaultDict
 
 REGEX_PATTERN_RELEVANT_KEYS = r"'([A-Za-z0-9_\./\\-]*)'"
 
 
-def get_diff_iterables_items(diff_result: Mapping) -> Dict:
+def get_diff_iterables_items(diff_result: Mapping) -> DefaultDict:
     """Helper function for diff_generator to postprocess changes reported by DeepDiff for iterables.
 
     DeepDiff iterable_items are returned when the source data is a list
@@ -24,7 +24,7 @@ def get_diff_iterables_items(diff_result: Mapping) -> Dict:
     get_dict_keys = re.compile(r"^root((\['\w.*'\])+)\[\d+\]$")
 
     defaultdict_list = partial(defaultdict, list)
-    result = defaultdict(defaultdict_list)
+    result = defaultdict(defaultdict_list)  # type: DefaultDict
 
     items_removed = diff_result.get("iterable_item_removed")
     if items_removed:
@@ -59,7 +59,7 @@ def fix_deepdiff_key_names(obj: Mapping) -> Dict:
         Dict: aggregated output, for example: {'7.7.7.7': {'is_enabled': {'new_value': False, 'old_value': True},
                                                 'is_up': {'new_value': False, 'old_value': True}}}
     """
-    result = {}
+    result = {}  # type: Dict
     for key, value in obj.items():
         key_parts = re.findall(REGEX_PATTERN_RELEVANT_KEYS, key)
         if not key_parts:  # If key parts can't be find, keep original key so data is not lost.
@@ -69,7 +69,6 @@ def fix_deepdiff_key_names(obj: Mapping) -> Dict:
     return result
 
 
-# TODO: Add testing
 def group_value(tree_list: List, value: Dict) -> Dict:
     """Function to create a nested Dict by recursively use the tree_list as nested keys."""
     if tree_list:
@@ -77,7 +76,6 @@ def group_value(tree_list: List, value: Dict) -> Dict:
     return value
 
 
-# TODO: Add testing
 def dict_merger(original_dict: Dict, dict_to_merge: Dict):
     """Function to merge a dictionary (dict_to_merge) recursively into the original_dict."""
     for key in dict_to_merge.keys():
diff --git a/netcompare/utils/jmespath_parsers.py b/netcompare/utils/jmespath_parsers.py
index 44e7e72..64ea838 100644
--- a/netcompare/utils/jmespath_parsers.py
+++ b/netcompare/utils/jmespath_parsers.py
@@ -1,11 +1,20 @@
-"""jmespath expression parsers and related utilities."""
+"""
+jmespath expression parsers and related utilities.
+
+This utility interfaces the custom netcompare jmespath expression with the jmespath library.
+From one expression defined in netcompare, we will derive two expressions: one expression that traverse the json output and get the
+evaluated bit of it, the second will target the reference key relative to the value to evaluate. More on README.md
+"""
 import re
 from typing import Mapping, List, Union
 
 
 def jmespath_value_parser(path: str):
     """
-    Get the jmespath value path from 'path'.
+    Extract the jmespath value path from 'path' argument.
+
+    This is required as we use custom anchors ($$) to identify the reference key.
+    So the expression must be parsed and stripped of the reference key anchor. More info on README.md
 
     Two combinations are possible based on where reference key is defined. See example below.
 
@@ -37,7 +46,10 @@ def jmespath_value_parser(path: str):
 
 def jmespath_refkey_parser(path: str):
     """
-    Get the jmespath reference key path from 'path'.
+    Get the jmespath reference key path from 'path' argument.
+
+    Reference key is define within $$ in 'path' and will be associated to the value/s to be evaluated.
+    More on README.md.
 
     Args:
         path: "result[0].vrfs.default.peerList[*].[$peerAddress$,prefixesReceived]"
@@ -59,7 +71,7 @@ def jmespath_refkey_parser(path: str):
 
 
 def associate_key_of_my_value(paths: str, wanted_value: List) -> List:
-    """Associate each key defined in path to every value found in output."""
+    """Associate each reference key (from: jmespath_refkey_parser) to every value found in output (from: jmespath_value_parser)."""
     # global.peers.*.[is_enabled,is_up] / result.[*].state
     find_the_key_of_my_values = paths.split(".")[-1]
 
@@ -85,7 +97,7 @@ def associate_key_of_my_value(paths: str, wanted_value: List) -> List:
 
 
 def keys_cleaner(wanted_reference_keys: Union[Mapping, List]) -> List:
-    """Get every required reference key from output."""
+    """Get every required reference key from output and build a dictionary from it."""
     if isinstance(wanted_reference_keys, list):
         final_result = wanted_reference_keys
     elif isinstance(wanted_reference_keys, dict):
diff --git a/poetry.lock b/poetry.lock
index 8606d44..e0afc00 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,6 +1,6 @@
 [[package]]
 name = "astroid"
-version = "2.11.2"
+version = "2.11.6"
 description = "An abstract syntax tree for Python with inference support."
 category = "dev"
 optional = false
@@ -36,11 +36,11 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
 
 [[package]]
 name = "bandit"
-version = "1.7.1"
+version = "1.7.4"
 description = "Security oriented static analyser for python code."
 category = "dev"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
 
 [package.dependencies]
 colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
@@ -48,6 +48,11 @@ GitPython = ">=1.0.1"
 PyYAML = ">=5.3.1"
 stevedore = ">=1.20.0"
 
+[package.extras]
+test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"]
+toml = ["toml"]
+yaml = ["pyyaml"]
+
 [[package]]
 name = "black"
 version = "22.3.0"
@@ -58,7 +63,6 @@ python-versions = ">=3.6.2"
 
 [package.dependencies]
 click = ">=8.0.0"
-dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
 mypy-extensions = ">=0.4.3"
 pathspec = ">=0.9.0"
 platformdirs = ">=2"
@@ -74,11 +78,11 @@ uvloop = ["uvloop (>=0.15.2)"]
 
 [[package]]
 name = "certifi"
-version = "2021.10.8"
+version = "2022.5.18.1"
 description = "Python package for providing Mozilla's CA Bundle."
 category = "dev"
 optional = false
-python-versions = "*"
+python-versions = ">=3.6"
 
 [[package]]
 name = "charset-normalizer"
@@ -93,11 +97,11 @@ unicode_backport = ["unicodedata2"]
 
 [[package]]
 name = "click"
-version = "8.0.4"
+version = "8.1.3"
 description = "Composable command line interface toolkit"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.dependencies]
 colorama = {version = "*", markers = "platform_system == \"Windows\""}
@@ -111,52 +115,44 @@ category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
-[[package]]
-name = "dataclasses"
-version = "0.8"
-description = "A backport of the dataclasses module for Python 3.6"
-category = "dev"
-optional = false
-python-versions = ">=3.6, <3.7"
-
 [[package]]
 name = "deepdiff"
-version = "5.7.0"
+version = "5.8.1"
 description = "Deep Difference and Search of any Python object/data."
 category = "main"
 optional = false
 python-versions = ">=3.6"
 
 [package.dependencies]
-ordered-set = "4.0.2"
+ordered-set = ">=4.1.0,<4.2.0"
 
 [package.extras]
 cli = ["click (==8.0.3)", "pyyaml (==5.4.1)", "toml (==0.10.2)", "clevercsv (==0.7.1)"]
 
 [[package]]
 name = "dill"
-version = "0.3.4"
+version = "0.3.5.1"
 description = "serialize all of python"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*"
 
 [package.extras]
 graph = ["objgraph (>=1.7.2)"]
 
 [[package]]
 name = "flake8"
-version = "3.9.2"
+version = "4.0.1"
 description = "the modular source code checker: pep8 pyflakes and co"
 category = "dev"
 optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.6"
 
 [package.dependencies]
-importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""}
 mccabe = ">=0.6.0,<0.7.0"
-pycodestyle = ">=2.7.0,<2.8.0"
-pyflakes = ">=2.3.0,<2.4.0"
+pycodestyle = ">=2.8.0,<2.9.0"
+pyflakes = ">=2.4.0,<2.5.0"
 
 [[package]]
 name = "gitdb"
@@ -171,15 +167,15 @@ smmap = ">=3.0.1,<6"
 
 [[package]]
 name = "gitpython"
-version = "3.1.20"
-description = "Python Git Library"
+version = "3.1.27"
+description = "GitPython is a python library used to interact with Git repositories"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.dependencies]
 gitdb = ">=4.0.1,<5"
-typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.10\""}
+typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""}
 
 [[package]]
 name = "idna"
@@ -191,7 +187,7 @@ python-versions = ">=3.5"
 
 [[package]]
 name = "importlib-metadata"
-version = "4.8.3"
+version = "4.2.0"
 description = "Read metadata from Python packages"
 category = "dev"
 optional = false
@@ -203,8 +199,7 @@ zipp = ">=0.5"
 
 [package.extras]
 docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-perf = ["ipython"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
+testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
 
 [[package]]
 name = "iniconfig"
@@ -216,7 +211,7 @@ python-versions = "*"
 
 [[package]]
 name = "invoke"
-version = "1.7.0"
+version = "1.7.1"
 description = "Pythonic task execution"
 category = "dev"
 optional = false
@@ -260,6 +255,25 @@ category = "dev"
 optional = false
 python-versions = "*"
 
+[[package]]
+name = "mypy"
+version = "0.961"
+description = "Optional static typing for Python"
+category = "dev"
+optional = false
+python-versions = ">=3.6"
+
+[package.dependencies]
+mypy-extensions = ">=0.4.3"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
+typing-extensions = ">=3.10"
+
+[package.extras]
+dmypy = ["psutil (>=4.0)"]
+python2 = ["typed-ast (>=1.4.0,<2)"]
+reports = ["lxml"]
+
 [[package]]
 name = "mypy-extensions"
 version = "0.4.3"
@@ -270,11 +284,14 @@ python-versions = "*"
 
 [[package]]
 name = "ordered-set"
-version = "4.0.2"
-description = "A set that remembers its order, and allows looking up its items by their index in that order."
+version = "4.1.0"
+description = "An OrderedSet is a custom MutableSet that remembers its order, so that every"
 category = "main"
 optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
+
+[package.extras]
+dev = ["pytest", "black", "mypy"]
 
 [[package]]
 name = "packaging"
@@ -297,7 +314,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 
 [[package]]
 name = "pbr"
-version = "5.8.1"
+version = "5.9.0"
 description = "Python Build Reasonableness"
 category = "dev"
 optional = false
@@ -305,15 +322,15 @@ python-versions = ">=2.6"
 
 [[package]]
 name = "platformdirs"
-version = "2.4.0"
+version = "2.5.2"
 description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.extras]
-docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
+test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
 
 [[package]]
 name = "pluggy"
@@ -340,11 +357,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "pycodestyle"
-version = "2.7.0"
+version = "2.8.0"
 description = "Python style guide checker"
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
 [[package]]
 name = "pydocstyle"
@@ -362,7 +379,7 @@ toml = ["toml"]
 
 [[package]]
 name = "pyflakes"
-version = "2.3.1"
+version = "2.4.0"
 description = "passive checker of Python programs"
 category = "dev"
 optional = false
@@ -370,14 +387,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
 [[package]]
 name = "pylint"
-version = "2.13.3"
+version = "2.13.9"
 description = "python code static checker"
 category = "dev"
 optional = false
 python-versions = ">=3.6.2"
 
 [package.dependencies]
-astroid = ">=2.11.2,<=2.12.0-dev0"
+astroid = ">=2.11.5,<=2.12.0-dev0"
 colorama = {version = "*", markers = "sys_platform == \"win32\""}
 dill = ">=0.2"
 isort = ">=4.2.5,<6"
@@ -391,22 +408,22 @@ testutil = ["gitpython (>3)"]
 
 [[package]]
 name = "pyparsing"
-version = "3.0.7"
-description = "Python parsing module"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.6.8"
 
 [package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
+diagrams = ["railroad-diagrams", "jinja2"]
 
 [[package]]
 name = "pytest"
-version = "7.0.1"
+version = "7.1.2"
 description = "pytest: simple powerful testing with Python"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.dependencies]
 atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
@@ -432,20 +449,20 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "requests"
-version = "2.27.1"
+version = "2.28.0"
 description = "Python HTTP for Humans."
 category = "dev"
 optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.7, <4"
 
 [package.dependencies]
 certifi = ">=2017.4.17"
-charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
-idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+charset-normalizer = ">=2.0.0,<2.1.0"
+idna = ">=2.5,<4"
 urllib3 = ">=1.21.1,<1.27"
 
 [package.extras]
-socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
 use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
 
 [[package]]
@@ -510,15 +527,15 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
 [[package]]
 name = "tomli"
-version = "1.2.3"
+version = "2.0.1"
 description = "A lil' TOML parser"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "typed-ast"
-version = "1.5.2"
+version = "1.5.4"
 description = "a fork of Python 2 and 3 ast modules with type comment support"
 category = "dev"
 optional = false
@@ -526,11 +543,11 @@ python-versions = ">=3.6"
 
 [[package]]
 name = "typing-extensions"
-version = "4.1.1"
-description = "Backported and Experimental Type Hints for Python 3.6+"
+version = "4.2.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [[package]]
 name = "urllib3"
@@ -547,7 +564,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
 [[package]]
 name = "wrapt"
-version = "1.14.0"
+version = "1.14.1"
 description = "Module for decorators, wrappers and monkey patching."
 category = "dev"
 optional = false
@@ -567,25 +584,25 @@ pyyaml = "*"
 
 [[package]]
 name = "zipp"
-version = "3.6.0"
+version = "3.8.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 category = "dev"
 optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
 
 [package.extras]
-docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
+docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
+testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
 
 [metadata]
 lock-version = "1.1"
-python-versions = "^3.6.2"
-content-hash = "9e8a9a3f7538a3546faae1b4f35e1113f166c2a34822e9f2e8746b61d55bbce8"
+python-versions = "^3.7.0"
+content-hash = "6137c91b7e7b479aa34d3fbc74a92ea0fc193a390f3bc251f2b0513c0d960978"
 
 [metadata.files]
 astroid = [
-    {file = "astroid-2.11.2-py3-none-any.whl", hash = "sha256:cc8cc0d2d916c42d0a7c476c57550a4557a083081976bf42a73414322a6411d9"},
-    {file = "astroid-2.11.2.tar.gz", hash = "sha256:8d0a30fe6481ce919f56690076eafbb2fb649142a89dc874f1ec0e7a011492d0"},
+    {file = "astroid-2.11.6-py3-none-any.whl", hash = "sha256:ba33a82a9a9c06a5ceed98180c5aab16e29c285b828d94696bf32d6015ea82a9"},
+    {file = "astroid-2.11.6.tar.gz", hash = "sha256:4f933d0bf5e408b03a6feb5d23793740c27e07340605f236496cd6ce552043d6"},
 ]
 atomicwrites = [
     {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
@@ -596,8 +613,8 @@ attrs = [
     {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
 ]
 bandit = [
-    {file = "bandit-1.7.1-py3-none-any.whl", hash = "sha256:f5acd838e59c038a159b5c621cf0f8270b279e884eadd7b782d7491c02add0d4"},
-    {file = "bandit-1.7.1.tar.gz", hash = "sha256:a81b00b5436e6880fa8ad6799bc830e02032047713cbb143a12939ac67eb756c"},
+    {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"},
+    {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"},
 ]
 black = [
     {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
@@ -625,60 +642,56 @@ black = [
     {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
 ]
 certifi = [
-    {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
-    {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
+    {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"},
+    {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"},
 ]
 charset-normalizer = [
     {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
     {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
 ]
 click = [
-    {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
-    {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
+    {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+    {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
 ]
 colorama = [
     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
     {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 ]
-dataclasses = [
-    {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
-    {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
-]
 deepdiff = [
-    {file = "deepdiff-5.7.0-py3-none-any.whl", hash = "sha256:1ffb38c3b5d9174eb2df95850c93aee55ec00e19396925036a2e680f725079e0"},
-    {file = "deepdiff-5.7.0.tar.gz", hash = "sha256:838766484e323dcd9dec6955926a893a83767dc3f3f94542773e6aa096efe5d4"},
+    {file = "deepdiff-5.8.1-py3-none-any.whl", hash = "sha256:e9aea49733f34fab9a0897038d8f26f9d94a97db1790f1b814cced89e9e0d2b7"},
+    {file = "deepdiff-5.8.1.tar.gz", hash = "sha256:8d4eb2c4e6cbc80b811266419cb71dd95a157094a3947ccf937a94d44943c7b8"},
 ]
 dill = [
-    {file = "dill-0.3.4-py2.py3-none-any.whl", hash = "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f"},
-    {file = "dill-0.3.4.zip", hash = "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"},
+    {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
+    {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"},
 ]
 flake8 = [
-    {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
-    {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
+    {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"},
+    {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"},
 ]
 gitdb = [
     {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
     {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
 ]
 gitpython = [
-    {file = "GitPython-3.1.20-py3-none-any.whl", hash = "sha256:b1e1c269deab1b08ce65403cf14e10d2ef1f6c89e33ea7c5e5bb0222ea593b8a"},
-    {file = "GitPython-3.1.20.tar.gz", hash = "sha256:df0e072a200703a65387b0cfdf0466e3bab729c0458cf6b7349d0e9877636519"},
+    {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"},
+    {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
 ]
 idna = [
     {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
     {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
 ]
 importlib-metadata = [
-    {file = "importlib_metadata-4.8.3-py3-none-any.whl", hash = "sha256:65a9576a5b2d58ca44d133c42a241905cc45e34d2c06fd5ba2bafa221e5d7b5e"},
-    {file = "importlib_metadata-4.8.3.tar.gz", hash = "sha256:766abffff765960fcc18003801f7044eb6755ffae4521c8e8ce8e83b9c9b0668"},
+    {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"},
+    {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"},
 ]
 iniconfig = [
     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
 ]
 invoke = [
-    {file = "invoke-1.7.0-py3-none-any.whl", hash = "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0"},
-    {file = "invoke-1.7.0.tar.gz", hash = "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c"},
+    {file = "invoke-1.7.1-py3-none-any.whl", hash = "sha256:2dc975b4f92be0c0a174ad2d063010c8a1fdb5e9389d69871001118b4fcac4fb"},
+    {file = "invoke-1.7.1.tar.gz", hash = "sha256:7b6deaf585eee0a848205d0b8c0014b9bf6f287a8eb798818a642dff1df14b19"},
 ]
 isort = [
     {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
@@ -731,12 +744,38 @@ mccabe = [
     {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
 ]
+mypy = [
+    {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"},
+    {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"},
+    {file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"},
+    {file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"},
+    {file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"},
+    {file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"},
+    {file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"},
+    {file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"},
+    {file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"},
+    {file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"},
+    {file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"},
+    {file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"},
+    {file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"},
+    {file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"},
+    {file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"},
+    {file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"},
+    {file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"},
+    {file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"},
+    {file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"},
+    {file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"},
+    {file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"},
+    {file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"},
+    {file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"},
+]
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
 ordered-set = [
-    {file = "ordered-set-4.0.2.tar.gz", hash = "sha256:ba93b2df055bca202116ec44b9bead3df33ea63a7d5827ff8e16738b97f33a95"},
+    {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"},
+    {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"},
 ]
 packaging = [
     {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
@@ -747,12 +786,12 @@ pathspec = [
     {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
 ]
 pbr = [
-    {file = "pbr-5.8.1-py2.py3-none-any.whl", hash = "sha256:27108648368782d07bbf1cb468ad2e2eeef29086affd14087a6d04b7de8af4ec"},
-    {file = "pbr-5.8.1.tar.gz", hash = "sha256:66bc5a34912f408bb3925bf21231cb6f59206267b7f63f3503ef865c1a292e25"},
+    {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"},
+    {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"},
 ]
 platformdirs = [
-    {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
-    {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
+    {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
+    {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
 ]
 pluggy = [
     {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
@@ -763,28 +802,28 @@ py = [
     {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
 ]
 pycodestyle = [
-    {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
-    {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
+    {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"},
+    {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"},
 ]
 pydocstyle = [
     {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
     {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"},
 ]
 pyflakes = [
-    {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
-    {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
+    {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"},
+    {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"},
 ]
 pylint = [
-    {file = "pylint-2.13.3-py3-none-any.whl", hash = "sha256:c8837b6ec6440e3490ab8f066054b0645a516a29ca51ce442f16f7004f711a70"},
-    {file = "pylint-2.13.3.tar.gz", hash = "sha256:12ed2520510c40db647e4ec7f747b07e0d669b33ab41479c2a07bb89b92877db"},
+    {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"},
+    {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"},
 ]
 pyparsing = [
-    {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
-    {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
+    {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+    {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
 ]
 pytest = [
-    {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"},
-    {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"},
+    {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
+    {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
 ]
 pyyaml = [
     {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
@@ -822,8 +861,8 @@ pyyaml = [
     {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
 ]
 requests = [
-    {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
-    {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
+    {file = "requests-2.28.0-py3-none-any.whl", hash = "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f"},
+    {file = "requests-2.28.0.tar.gz", hash = "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b"},
 ]
 requests-mock = [
     {file = "requests-mock-1.9.3.tar.gz", hash = "sha256:8d72abe54546c1fc9696fa1516672f1031d72a55a1d66c85184f972a24ba0eba"},
@@ -850,113 +889,113 @@ toml = [
     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
 ]
 tomli = [
-    {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"},
-    {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"},
+    {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+    {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
 ]
 typed-ast = [
-    {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"},
-    {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"},
-    {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"},
-    {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"},
-    {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"},
-    {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"},
-    {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"},
-    {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"},
-    {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"},
-    {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"},
-    {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"},
-    {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"},
-    {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"},
-    {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"},
-    {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"},
-    {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"},
-    {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"},
-    {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"},
-    {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"},
-    {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"},
-    {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"},
-    {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"},
-    {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"},
-    {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"},
+    {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"},
+    {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"},
+    {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"},
+    {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"},
+    {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"},
+    {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"},
+    {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"},
+    {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"},
+    {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"},
+    {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"},
+    {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"},
+    {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"},
+    {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"},
+    {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"},
+    {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"},
+    {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"},
+    {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
+    {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
 ]
 typing-extensions = [
-    {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"},
-    {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"},
+    {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
+    {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
 ]
 urllib3 = [
     {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"},
     {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"},
 ]
 wrapt = [
-    {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"},
-    {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"},
-    {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"},
-    {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"},
-    {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"},
-    {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"},
-    {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"},
-    {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"},
-    {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"},
-    {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"},
-    {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"},
-    {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"},
-    {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"},
-    {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"},
-    {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"},
-    {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"},
-    {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"},
-    {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"},
-    {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"},
-    {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"},
-    {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"},
-    {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"},
-    {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"},
-    {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"},
-    {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"},
-    {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"},
-    {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"},
-    {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"},
-    {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"},
-    {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"},
-    {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"},
-    {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"},
-    {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"},
-    {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"},
-    {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"},
-    {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"},
-    {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"},
-    {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"},
-    {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"},
-    {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"},
-    {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"},
-    {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"},
-    {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"},
-    {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"},
-    {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"},
-    {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"},
-    {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"},
-    {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"},
-    {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"},
-    {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"},
-    {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"},
-    {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"},
-    {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"},
-    {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"},
-    {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"},
-    {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"},
-    {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"},
-    {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"},
-    {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"},
-    {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"},
-    {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"},
-    {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"},
-    {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"},
-    {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"},
+    {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"},
+    {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"},
+    {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"},
+    {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"},
+    {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"},
+    {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"},
+    {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"},
+    {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"},
+    {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"},
+    {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"},
+    {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"},
+    {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"},
+    {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"},
+    {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"},
+    {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"},
+    {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"},
+    {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"},
+    {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"},
+    {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"},
+    {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"},
+    {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"},
+    {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"},
+    {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"},
+    {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"},
+    {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"},
+    {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"},
+    {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"},
+    {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"},
+    {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"},
+    {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"},
+    {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"},
+    {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"},
+    {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"},
+    {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"},
+    {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"},
+    {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"},
+    {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"},
+    {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"},
+    {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"},
+    {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"},
+    {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"},
+    {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"},
+    {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"},
+    {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"},
+    {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"},
+    {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"},
+    {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"},
+    {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"},
+    {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"},
+    {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"},
+    {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"},
+    {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"},
+    {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"},
+    {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"},
+    {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"},
 ]
 yamllint = [
     {file = "yamllint-1.26.3.tar.gz", hash = "sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e"},
 ]
 zipp = [
-    {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"},
-    {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"},
+    {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
+    {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
 ]
diff --git a/pyproject.toml b/pyproject.toml
index 10b9139..400c9e8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,7 +10,6 @@ readme = "README.md"
 keywords = ["json", "diff", "network"]
 classifiers = [
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.6",
     "Programming Language :: Python :: 3.7",
     "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
@@ -23,7 +22,7 @@ include = [
 ]
 
 [tool.poetry.dependencies]
-python = "^3.6.2"
+python = "^3.7.0"
 deepdiff = "^5.5.0"
 jmespath = "^0.10.0"
 
@@ -39,7 +38,7 @@ bandit = "*"
 invoke = "*"
 toml = "*"
 flake8 = "*"
-
+mypy = "*"
 
 [tool.black]
 line-length = 120
@@ -71,12 +70,13 @@ no-docstring-rgx="^(_|test_|Meta$)"
 [tool.pylint.messages_control]
 # Line length is enforced by Black, so pylint doesn't need to check it.
 # Pylint and Black disagree about how to format multi-line arrays; Black wins.
+# too-many-branches disabled to supported nested logic in Operator.
+# protected-access disabled as we want test the method
 disable = """,
     line-too-long,
     bad-continuation,
-    E5110,
-    R0912,
-    R0801
+    too-many-branches,
+    protected-access
     """
 
 [tool.pylint.miscellaneous]
@@ -107,3 +107,19 @@ testpaths = [
     "tests"
 ]
 addopts = "-vv --doctest-modules"
+
+[tool.mypy]
+exclude = [
+    '^tasks\.py',
+]
+
+[[tool.mypy.overrides]]
+module = [
+    "deepdiff",
+    "jmespath",
+]
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "tests.*"
+ignore_errors = true
diff --git a/tasks.py b/tasks.py
index 6b20a26..aa89212 100644
--- a/tasks.py
+++ b/tasks.py
@@ -30,7 +30,7 @@ def is_truthy(arg):
 TOOL_CONFIG = PYPROJECT_CONFIG["tool"]["poetry"]
 
 # Can be set to a separate Python version to be used for launching or building image
-PYTHON_VER = os.getenv("PYTHON_VER", "3.6")
+PYTHON_VER = os.getenv("PYTHON_VER", "3.7")
 # Name of the docker image/image
 IMAGE_NAME = os.getenv("IMAGE_NAME", TOOL_CONFIG["name"])
 # Tag for the image
@@ -148,6 +148,13 @@ def bandit(context, path=".", local=INVOKE_LOCAL):
     run_cmd(context, exec_cmd, local)
 
 
+@task(help={"local": "Run locally or within the Docker container"})
+def mypy(context, path=".", local=INVOKE_LOCAL):
+    """Run mypy to validate type hinting."""
+    exec_cmd = f"mypy {path}"
+    run_cmd(context, exec_cmd, local)
+
+
 @task
 def cli(context):
     """Enter the image to perform troubleshooting or dev work."""
@@ -164,6 +171,6 @@ def tests(context, path=".", local=INVOKE_LOCAL):
     yamllint(context, path, local)
     pydocstyle(context, path, local)
     bandit(context, path, local)
+    mypy(context, path, local)
     pytest(context, local)
-
     print("All tests have passed!")


Test patch:
diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/test_data_normalization.py b/tests/test_data_normalization.py
index 6d5760e..bc28661 100644
--- a/tests/test_data_normalization.py
+++ b/tests/test_data_normalization.py
@@ -16,5 +16,6 @@
 
 @pytest.mark.parametrize("data, expected_output", flatten_list_tests)
 def test_value_parser(data, expected_output):
+    """Assert that flatten_list function flat a multiple nested list into a list of lists."""
     output = flatten_list(data)
     assert expected_output == output, ASSERT_FAIL_MESSAGE.format(output=output, expected_output=expected_output)
diff --git a/tests/test_diff_generator.py b/tests/test_diff_generator.py
index 03b1a20..baaedc3 100644
--- a/tests/test_diff_generator.py
+++ b/tests/test_diff_generator.py
@@ -5,7 +5,8 @@
 from .utility import load_mocks, ASSERT_FAIL_MESSAGE
 
 
-exact_match_of_global_peers_via_napalm_getter = (
+# Diff test case 1.
+global_peers = (
     "napalm_getter_peer_state_change",
     "global.$peers$.*.[is_enabled,is_up]",
     [],
@@ -21,21 +22,24 @@
     },
 )
 
-exact_match_of_bgp_peer_caps_via_api = (
+# Diff test case 2.
+bgp_peer_caps = (
     "api",
     "result[0].vrfs.default.peerList[*].[$peerAddress$,state,bgpPeerCaps]",
     [],
     {"7.7.7.7": {"state": {"new_value": "Connected", "old_value": "Idle"}}},
 )
 
-exact_match_of_bgp_neigh_via_textfsm = (
+# Diff test case 3.
+bgp_neigh = (
     "textfsm",
     "result[*].[$bgp_neigh$,state]",
     [],
     {"10.17.254.2": {"state": {"new_value": "Up", "old_value": "Idle"}}},
 )
 
-raw_diff_of_interface_ma1_via_api_value_exclude = (
+# Diff test case 4. Exclude filter applied.
+raw_diff_of_interface_ma1_value_exclude = (
     "raw_value_exclude",
     "result[*]",
     ["interfaceStatistics", "interfaceCounters"],
@@ -49,7 +53,8 @@
     },
 )
 
-raw_diff_of_interface_ma1_via_api_novalue_exclude = (
+# Diff test case 5. Raw diff (no jmspath expression defined) and exclude filter applied.
+raw_diff_of_interface_ma1_novalue_exclude = (
     "raw_novalue_exclude",
     None,
     ["interfaceStatistics", "interfaceCounters"],
@@ -68,7 +73,8 @@
     },
 )
 
-raw_diff_of_interface_ma1_via_api_novalue_noexclude = (
+# Diff test case 5. Raw diff (no jmspath expression defined) and no exclude filter applied.
+raw_diff_of_interface_ma1_novalue_noexclude = (
     "raw_novalue_noexclude",
     None,
     [],
@@ -101,28 +107,32 @@
     },
 )
 
-exact_match_missing_item = (
+# Diff test case 6. Missing item in post.
+missing_item = (
     "napalm_getter_missing_peer",
     None,
     [],
     {"global": {"peers": {"7.7.7.7": "missing"}}},
 )
 
-exact_match_additional_item = (
+# Diff test case 7. Extra item in post.
+additional_item = (
     "napalm_getter_additional_peer",
     None,
     [],
     {"global": {"peers": {"8.8.8.8": "new"}}},
 )
 
-exact_match_changed_item = (
+# Diff test case 8. Item changed in post.
+changed_item = (
     "napalm_getter_changed_peer",
     None,
     [],
     {"global": {"peers": {"7.7.7.7": "missing", "8.8.8.8": "new"}}},
 )
 
-exact_match_multi_nested_list = (
+# Diff test case 9. Value in multiple nested lists.
+multi_nested_list = (
     "exact_match_nested",
     "global.$peers$.*.*.ipv4.[accepted_prefixes,received_prefixes]",
     [],
@@ -132,14 +142,16 @@
     },
 )
 
-exact_match_textfsm_ospf_int_br_raw = (
+# Diff test case 10. JMSPath empty string.
+ospf_int_br_raw = (
     "textfsm_ospf_int_br",
     "",
     [],
     {"state": {"new_value": "DOWN", "old_value": "P2P", "new_value_dup!": "DR", "old_value_dup!": "BDR"}},
 )
 
-exact_match_textfsm_ospf_int_br_normalized = (
+# Diff test case 11. Extensive nu,ber of values.
+ospf_int_br_normalized = (
     "textfsm_ospf_int_br",
     "[*].[$interface$,area,ip_address_mask,cost,state,neighbors_fc]",
     [],
@@ -149,14 +161,16 @@
     },
 )
 
-exact_match_test_issue_44_case_1 = (
+# Test GitLab Issues
+# Test issue #44: https://github.com/networktocode-llc/netcompare/issues/44
+test_issue_44_case_1 = (
     "raw_novalue_exclude",
     "result[*].interfaces.*.[$name$,interfaceStatus]",
     [],
     {"Management1": {"interfaceStatus": {"new_value": "connected", "old_value": "down"}}},
 )
 
-exact_match_test_issue_44_case_2 = (
+test_issue_44_case_2 = (
     "raw_novalue_exclude",
     "result[0].interfaces.*.[$name$,interfaceStatus]",
     [],
@@ -165,20 +179,20 @@
 
 
 eval_tests = [
-    exact_match_of_global_peers_via_napalm_getter,
-    exact_match_of_bgp_peer_caps_via_api,
-    exact_match_of_bgp_neigh_via_textfsm,
-    raw_diff_of_interface_ma1_via_api_value_exclude,
-    raw_diff_of_interface_ma1_via_api_novalue_exclude,
-    raw_diff_of_interface_ma1_via_api_novalue_noexclude,
-    exact_match_missing_item,
-    exact_match_additional_item,
-    exact_match_changed_item,
-    exact_match_multi_nested_list,
-    exact_match_textfsm_ospf_int_br_raw,
-    exact_match_textfsm_ospf_int_br_normalized,
-    exact_match_test_issue_44_case_1,
-    exact_match_test_issue_44_case_2,
+    global_peers,
+    bgp_peer_caps,
+    bgp_neigh,
+    raw_diff_of_interface_ma1_value_exclude,
+    raw_diff_of_interface_ma1_novalue_exclude,
+    raw_diff_of_interface_ma1_novalue_noexclude,
+    missing_item,
+    additional_item,
+    changed_item,
+    multi_nested_list,
+    ospf_int_br_raw,
+    ospf_int_br_normalized,
+    test_issue_44_case_1,
+    test_issue_44_case_2,
 ]
 
 
diff --git a/tests/test_diff_helpers.py b/tests/test_diff_helpers.py
new file mode 100644
index 0000000..be7eb03
--- /dev/null
+++ b/tests/test_diff_helpers.py
@@ -0,0 +1,46 @@
+"""DIff helpers unit tests."""
+from netcompare.utils.diff_helpers import dict_merger, group_value, fix_deepdiff_key_names, get_diff_iterables_items
+
+
+def test_dict_merger():
+    """Tests that dict is merged as expected and duplicates identified."""
+    original_dict = dict(key_1="my_key_1", key_5="my_key_5")
+    dict_to_merge = dict(key_1="my_key_1", key_2="my_key_2", key_3="my_key_3")
+    dict_merger(original_dict, dict_to_merge)
+
+    assert original_dict == {
+        "key_1": "my_key_1",
+        "key_1_dup!": "my_key_1",
+        "key_2": "my_key_2",
+        "key_3": "my_key_3",
+        "key_5": "my_key_5",
+    }
+
+
+def test_group_value():
+    """Tests that nested dict is recursively created."""
+    tree_list = ["10.1.0.0", "is_enabled"]
+    value = {"new_value": False, "old_value": True}
+    assert group_value(tree_list, value) == {"10.1.0.0": {"is_enabled": {"new_value": False, "old_value": True}}}
+
+
+def test_fix_deepdiff_key_names():
+    """Tests that deepdiff return is parsed properly."""
+    deepdiff_object = {"root[0]['10.1.0.0']['is_enabled']": {"new_value": False, "old_value": True}}
+    assert fix_deepdiff_key_names(deepdiff_object) == {
+        "10.1.0.0": {"is_enabled": {"new_value": False, "old_value": True}}
+    }
+
+
+def test_get_diff_iterables_items():
+    """Tests that deepdiff return is parsed properly."""
+    diff_result = {
+        "values_changed": {"root['Ethernet1'][0]['port']": {"new_value": "518", "old_value": "519"}},
+        "iterable_item_added": {
+            "root['Ethernet3'][1]": {"hostname": "ios-xrv-unittest", "port": "Gi0/0/0/0"},
+        },
+    }
+    result = get_diff_iterables_items(diff_result)
+
+    assert list(dict(result).keys())[0] == "['Ethernet3']"
+    assert list(list(dict(result).values())[0].values())[0] == [{"hostname": "ios-xrv-unittest", "port": "Gi0/0/0/0"}]
diff --git a/tests/test_filter_parsers.py b/tests/test_filter_parsers.py
index d98137f..a80f4a3 100644
--- a/tests/test_filter_parsers.py
+++ b/tests/test_filter_parsers.py
@@ -4,7 +4,7 @@
 from .utility import ASSERT_FAIL_MESSAGE
 
 
-exclude_filter_case_1 = (
+exclude_filter_test_case_1 = (
     ["interfaceStatistics"],
     {
         "interfaces": {
@@ -30,11 +30,12 @@
 )
 
 exclude_filter_tests = [
-    exclude_filter_case_1,
+    exclude_filter_test_case_1,
 ]
 
 
 @pytest.mark.parametrize("exclude, data, expected_output", exclude_filter_tests)
 def test_exclude_filter(exclude, data, expected_output):
+    """Test exclude filter functionality."""
     exclude_filter(data, exclude)
     assert expected_output == data, ASSERT_FAIL_MESSAGE.format(output=data, expected_output=expected_output)
diff --git a/tests/test_sw_upgrade_device_state.py b/tests/test_sw_upgrade_device_state.py
index 18f42fb..683b74d 100644
--- a/tests/test_sw_upgrade_device_state.py
+++ b/tests/test_sw_upgrade_device_state.py
@@ -6,7 +6,7 @@
 
 
 @pytest.mark.parametrize(
-    "platform, command, jpath, expected_parameter, test_is_passed",
+    "platform, command, jpath, expected_parameter, check_should_pass",
     [
         ("arista_eos", "show_version", "[*].[$image$,image]", {"image": "4.14.7M"}, True),
         ("arista_eos", "show_version", "[*].[$image$,image]", {"image": "no-match"}, False),
@@ -16,7 +16,7 @@
         ("cisco_nxos", "show_version", "[*].[$os$,os]", {"os": "no-match"}, False),
     ],
 )
-def test_show_version(platform, command, jpath, expected_parameter, test_is_passed):
+def test_show_version(platform, command, jpath, expected_parameter, check_should_pass):
     """Test expected version with parameter_match."""
     filename = f"{platform}_{command}.json"
     command = load_json_file("sw_upgrade", filename)
@@ -24,11 +24,11 @@ def test_show_version(platform, command, jpath, expected_parameter, test_is_pass
     check = CheckType.init("parameter_match")
     value = check.get_value(command, jpath)
     eval_results, passed = check.evaluate(value, expected_parameter, "match")  # pylint: disable=E1121
-    assert passed is test_is_passed, f"FAILED, eval_result: {eval_results}"
+    assert passed is check_should_pass, f"FAILED, eval_result: {eval_results}"
 
 
 @pytest.mark.parametrize(
-    "platform, command, jpath, test_is_passed",
+    "platform, command, jpath, check_should_pass",
     [
         ("arista_eos", "show_interface", "[*].[$interface$,link_status,protocol_status]", True),
         ("cisco_ios", "show_interface", "[*].[$interface$,link_status,protocol_status]", True),
@@ -38,12 +38,12 @@ def test_show_version(platform, command, jpath, expected_parameter, test_is_pass
         ("cisco_nxos", "show_interface", "[*].[$interface$,link_status,admin_state]", False),
     ],
 )
-def test_show_interfaces_state(platform, command, jpath, test_is_passed):
+def test_show_interfaces_state(platform, command, jpath, check_should_pass):
     """Test the interface status with exact_match."""
     command_pre = load_json_file("sw_upgrade", f"{platform}_{command}.json")
     command_post = deepcopy(command_pre)
 
-    if test_is_passed is False:
+    if check_should_pass is False:
         if platform == "cisco_nxos":
             command_post[1]["admin_state"] = "down"
         else:
@@ -54,7 +54,7 @@ def test_show_interfaces_state(platform, command, jpath, test_is_passed):
     pre_value = CheckType.get_value(command_pre, jpath)
     post_value = CheckType.get_value(command_post, jpath)
     eval_results, passed = check.evaluate(post_value, pre_value)
-    assert passed is test_is_passed, f"FAILED, eval_result: {eval_results}"
+    assert passed is check_should_pass, f"FAILED, eval_result: {eval_results}"
 
 
 @pytest.mark.parametrize(
@@ -95,7 +95,7 @@ def test_show_ip_route_missing_and_additional_routes(platform, command):
 
 
 @pytest.mark.parametrize(
-    "platform, command, jpath, test_is_passed",
+    "platform, command, jpath, check_should_pass",
     [
         ("arista_eos", "show_ip_bgp_summary", "[*].[$bgp_neigh$,state]", True),
         ("arista_eos", "show_ip_bgp_summary", "[*].[$bgp_neigh$,state]", False),
@@ -105,12 +105,12 @@ def test_show_ip_route_missing_and_additional_routes(platform, command):
         ("cisco_nxos", "show_ip_bgp_neighbors", "[*].[$neighbor$,bgp_state]", False),
     ],
 )
-def test_bgp_neighbor_state(platform, command, jpath, test_is_passed):
+def test_bgp_neighbor_state(platform, command, jpath, check_should_pass):
     """Test bgp neighbors state with exact_match."""
     command_pre = load_json_file("sw_upgrade", f"{platform}_{command}.json")
     command_post = deepcopy(command_pre)
 
-    if test_is_passed is False:
+    if check_should_pass is False:
         state_key = "state" if "arista" in platform else "bgp_state"
         command_post[0][state_key] = "Idle"
 
@@ -118,11 +118,11 @@ def test_bgp_neighbor_state(platform, command, jpath, test_is_passed):
     pre_value = CheckType.get_value(command_pre, jpath)
     post_value = CheckType.get_value(command_post, jpath)
     eval_results, passed = check.evaluate(post_value, pre_value)
-    assert passed is test_is_passed, f"FAILED, eval_result: {eval_results}"
+    assert passed is check_should_pass, f"FAILED, eval_result: {eval_results}"
 
 
 @pytest.mark.parametrize(
-    "platform, command, prfx_post_value, tolerance, test_is_passed",
+    "platform, command, prfx_post_value, tolerance, check_should_pass",
     [
         ("cisco_ios", "show_ip_bgp_summary", "5457", 10, True),
         ("cisco_ios", "show_ip_bgp_summary", "5456", 10, False),
@@ -132,7 +132,7 @@ def test_bgp_neighbor_state(platform, command, jpath, test_is_passed):
         ("cisco_nxos", "show_ip_bgp_summary", "Idle", 10, False),
     ],
 )
-def test_bgp_prefix_tolerance(platform, command, prfx_post_value, tolerance, test_is_passed):
+def test_bgp_prefix_tolerance(platform, command, prfx_post_value, tolerance, check_should_pass):
     """Test bgp peer prefix count with tolerance."""
     command_pre = load_json_file("sw_upgrade", f"{platform}_{command}.json")
     command_post = deepcopy(command_pre)
@@ -145,11 +145,11 @@ def test_bgp_prefix_tolerance(platform, command, prfx_post_value, tolerance, tes
     post_value = CheckType.get_value(command_post, jpath)
 
     eval_results, passed = check.evaluate(post_value, pre_value, tolerance)  # pylint: disable=E1121
-    assert passed is test_is_passed, f"FAILED, eval_result: {eval_results}"
+    assert passed is check_should_pass, f"FAILED, eval_result: {eval_results}"
 
 
 @pytest.mark.parametrize(
-    "platform, command, jpath, test_is_passed",
+    "platform, command, jpath, check_should_pass",
     [
         ("arista_eos", "show_ip_ospf_neighbors", "[*].[$neighbor_id$,state]", True),
         ("arista_eos", "show_ip_ospf_neighbors", "[*].[$neighbor_id$,state]", False),
@@ -159,12 +159,12 @@ def test_bgp_prefix_tolerance(platform, command, prfx_post_value, tolerance, tes
         ("cisco_nxos", "show_ip_ospf_neighbors", "[*].[$neighbor_ipaddr$,state]", False),
     ],
 )
-def test_ospf_neighbor_state(platform, command, jpath, test_is_passed):
+def test_ospf_neighbor_state(platform, command, jpath, check_should_pass):
     """Test ospf neighbors with exact_match."""
     command_pre = load_json_file("sw_upgrade", f"{platform}_{command}.json")
     command_post = deepcopy(command_pre)
 
-    if test_is_passed is False:
+    if check_should_pass is False:
         command_post[0]["state"] = "2WAY"
         command_post = command_post[:1]
 
@@ -172,7 +172,7 @@ def test_ospf_neighbor_state(platform, command, jpath, test_is_passed):
     pre_value = CheckType.get_value(command_pre, jpath)
     post_value = CheckType.get_value(command_post, jpath)
     eval_results, passed = check.evaluate(post_value, pre_value)
-    assert passed is test_is_passed, f"FAILED, eval_result: {eval_results}"
+    assert passed is check_should_pass, f"FAILED, eval_result: {eval_results}"
 
 
 @pytest.mark.skip(reason="Command output not available")
@@ -181,7 +181,7 @@ def test_pim_neighbors():
 
 
 @pytest.mark.parametrize(
-    "platform, command, test_is_passed",
+    "platform, command, check_should_pass",
     [
         ("arista_eos", "show_lldp_neighbors", True),
         ("arista_eos", "show_lldp_neighbors", False),
@@ -191,14 +191,14 @@ def test_pim_neighbors():
         ("cisco_nxos", "show_lldp_neighbors", False),
     ],
 )
-def test_lldp_neighbor_state(platform, command, test_is_passed):
+def test_lldp_neighbor_state(platform, command, check_should_pass):
     """Test LLDP neighbors with exact match."""
     command_pre = load_json_file("sw_upgrade", f"{platform}_{command}.json")
     command_post = deepcopy(command_pre)
 
-    if test_is_passed is False:
+    if check_should_pass is False:
         command_post = command_post[:2]
 
     check = CheckType.init("exact_match")
     eval_results, passed = check.evaluate(command_post, command_pre)
-    assert passed is test_is_passed, f"FAILED, eval_result: {eval_results}"
+    assert passed is check_should_pass, f"FAILED, eval_result: {eval_results}"
diff --git a/tests/test_type_checks.py b/tests/test_type_checks.py
index a2a30ce..99ac67a 100644
--- a/tests/test_type_checks.py
+++ b/tests/test_type_checks.py
@@ -15,7 +15,7 @@ class CheckTypeChild(CheckType):
 
     assert (
         "Can't instantiate abstract class CheckTypeChild"
-        " with abstract methods evaluate, validate" in error.value.__str__()
+        " with abstract methods _validate, evaluate" in error.value.__str__()
     )
 
 
@@ -26,14 +26,14 @@ class CheckTypeChild(CheckType):
         """Test Class."""
 
         @staticmethod
-        def validate(**kwargs):
+        def _validate(*args):
             return None
 
         def evaluate(self, *args, **kwargs):
             return {}, True
 
     check = CheckTypeChild()
-    assert isinstance(check, CheckTypeChild) and check.validate() is None and check.evaluate() == ({}, True)
+    assert isinstance(check, CheckTypeChild) and check._validate() is None and check.evaluate() == ({}, True)
 
 
 @pytest.mark.parametrize(
diff --git a/tests/test_validates.py b/tests/test_validates.py
index 90507f1..bd7d2db 100644
--- a/tests/test_validates.py
+++ b/tests/test_validates.py
@@ -10,7 +10,7 @@
 tolerance_wrong_value = (
     "tolerance",
     {"tolerance": "10"},
-    "Tolerance argument's value must be an integer. You have: <class 'str'>.",
+    "Tolerance argument's value must be a number. You have: <class 'str'>.",
 )
 parameter_no_params = (
     "parameter_match",
@@ -122,11 +122,18 @@
 
 
 @pytest.mark.parametrize("check_type_str, evaluate_args, expected_results", all_tests)
-def test_tolerance_key_name(check_type_str, evaluate_args, expected_results):
+def test_validate_arguments(check_type_str, evaluate_args, expected_results):
     """Test CheckType validate method for each check-type."""
     check = CheckType.init(check_type_str)
 
     with pytest.raises(ValueError) as exc_info:
-        check.validate(**evaluate_args)
+        if check_type_str == "tolerance":
+            check._validate(evaluate_args.get("tolerance"))
+        elif check_type_str == "parameter_match":
+            check._validate(params=evaluate_args.get("params"), mode=evaluate_args.get("mode"))
+        elif check_type_str == "regex":
+            check._validate(regex=evaluate_args.get("regex"), mode=evaluate_args.get("mode"))
+        elif check_type_str == "operator":
+            check._validate(evaluate_args)
 
     assert exc_info.type is ValueError and exc_info.value.args[0] == expected_results
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
deleted file mode 100644
index c8f7298..0000000
--- a/tests/unit/conftest.py
+++ /dev/null
@@ -1,13 +0,0 @@
-"""Used to setup fixtures to be used through tests"""
-import pytest
-
-
-@pytest.fixture()
-def give_me_success():
-    """
-    Provides True to make tests pass
-
-    Returns:
-        (bool): Returns True
-    """
-    return True
diff --git a/tests/unit/test_initial_pass.py b/tests/unit/test_initial_pass.py
deleted file mode 100644
index 694c27e..0000000
--- a/tests/unit/test_initial_pass.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Example Test using Fixtures."""
-
-
-def test_initial_success(give_me_success):
-    "Assert give_me_success is true"
-    assert give_me_success