diff --git a/candore/__init__.py b/candore/__init__.py index 11bd0fa..f31b3d1 100644 --- a/candore/__init__.py +++ b/candore/__init__.py @@ -50,6 +50,7 @@ def compare_entities( self, pre_file=None, post_file=None, + inverse=None, output=None, report_type=None, record_evs=None, @@ -57,9 +58,9 @@ def compare_entities( comp = Comparator(settings=self.settings) if record_evs: comp.record_evs = True - results = comp.compare_json(pre_file=pre_file, post_file=post_file) + results = comp.compare_json(pre_file=pre_file, post_file=post_file, inverse=inverse) reporter = Reporting(results=results) - reporter.generate_report(output_file=output, output_type=report_type) + reporter.generate_report(output_file=output, output_type=report_type, inverse=inverse) def find_path(self, path, json_file, delimiter): finder = Finder() diff --git a/candore/cli.py b/candore/cli.py index b8d1f1d..d161e54 100644 --- a/candore/cli.py +++ b/candore/cli.py @@ -54,6 +54,7 @@ def extract(ctx, mode, output, full): @candore.command(help="Compare pre and post upgrade data") @click.option("--pre", type=str, help="The pre upgrade json file") @click.option("--post", type=str, help="The post upgrade json file") +@click.option("-i", "--inverse", is_flag=True, help="Inverse comparison, shows whats not changed") @click.option("-o", "--output", type=str, help="The output file name") @click.option( "-t", @@ -64,11 +65,12 @@ def extract(ctx, mode, output, full): ) @click.option("--record-evs", is_flag=True, help="Record Expected Variations in reporting") @click.pass_context -def compare(ctx, pre, post, output, report_type, record_evs): +def compare(ctx, pre, post, inverse, output, report_type, record_evs): candore_obj = ctx.parent.candore candore_obj.compare_entities( pre_file=pre, post_file=post, + inverse=inverse, output=output, report_type=report_type, record_evs=record_evs, diff --git a/candore/modules/comparator.py b/candore/modules/comparator.py index cc55c3c..19bab14 100644 --- a/candore/modules/comparator.py +++ b/candore/modules/comparator.py @@ -1,19 +1,20 @@ import json from candore.modules.variatons import Variations -from candore.utils import last_index_of_element +from candore.utils import last_index_of_element, is_list_contains_dict class Comparator: def __init__(self, settings): self.big_key = [] self.big_compare = {} + self.big_constant = {} self.record_evs = False self.variations = Variations(settings) self.expected_variations = self.variations.expected_variations self.skipped_variations = self.variations.skipped_variations - def remove_non_variant_key(self, key): + def remove_verifed_key(self, key): reversed_bk = self.big_key[::-1] if key in reversed_bk: reversed_bk.remove(key) @@ -44,6 +45,13 @@ def record_variation(self, pre, post, var_details=None): variation = {"pre": pre, "post": post, "variation": var_details or ""} self.big_compare.update({full_path: variation}) + def record_constants(self, pre, post): + big_key = [str(itm) for itm in self.big_key] + full_path = "/".join(big_key) + # var_full_path = "/".join([itm for itm in self.big_key if not isinstance(itm, int)]) + variation = {"pre": pre, "post": post} + self.big_constant.update({full_path: variation}) + def _is_data_type_dict(self, pre, post, unique_key=""): if (pre and 'id' in pre) and (post and 'id' in post): # Dont compare the entities if the ids are not the same @@ -62,32 +70,41 @@ def _is_data_type_dict(self, pre, post, unique_key=""): ) self.remove_path(unique_key) - def _is_data_type_list(self, pre, post, unique_key=""): + def _is_data_type_list_contains_dict(self, pre, post): for pre_entity in pre: if not pre_entity: continue - if type(pre_entity) is dict: - for post_entity in post: - if not post_entity: - continue - if "id" in pre_entity: - if pre_entity["id"] == post_entity["id"]: - self.compare_all_pres_with_posts( - pre_entity, post_entity, unique_key=pre_entity["id"] - ) - else: - key = list(pre_entity.keys())[0] - if pre_entity[key] == post_entity[key]: - self.compare_all_pres_with_posts( - pre_entity[key], post_entity[key], unique_key=key - ) + for post_entity in post: + if not post_entity: + continue if "id" in pre_entity: - self.remove_path(pre_entity["id"]) + if pre_entity["id"] == post_entity["id"]: + self.compare_all_pres_with_posts( + pre_entity, post_entity, unique_key=pre_entity["id"] + ) else: - self.remove_path(pre_entity[list(pre_entity.keys())[0]]) + key = list(pre_entity.keys())[0] + if pre_entity[key] == post_entity[key]: + self.compare_all_pres_with_posts( + pre_entity[key], post_entity[key], unique_key=key + ) + if "id" in pre_entity: + self.remove_path(pre_entity["id"]) + else: + self.remove_path(pre_entity[list(pre_entity.keys())[0]]) + + def _is_data_type_list(self, pre, post, unique_key=""): + + def custom_key(elem): + return 'None' if elem is None else elem + + if not is_list_contains_dict(pre): + if sorted(pre, key=custom_key) != sorted(post, key=custom_key): + self.record_variation(pre, post) else: - if pre_entity not in post: - self.record_variation(pre, post) + self.record_constants(pre, post) + else: + self._is_data_type_list_contains_dict(pre, post) self.remove_path(unique_key) def compare_all_pres_with_posts(self, pre_data, post_data, unique_key="", var_details=None): @@ -100,9 +117,11 @@ def compare_all_pres_with_posts(self, pre_data, post_data, unique_key="", var_de else: if pre_data != post_data: self.record_variation(pre_data, post_data, var_details) - self.remove_non_variant_key(unique_key) + else: + self.record_constants(pre_data, post_data) + self.remove_verifed_key(unique_key) - def compare_json(self, pre_file, post_file): + def compare_json(self, pre_file, post_file, inverse): pre_data = post_data = None with open(pre_file, "r") as fpre: @@ -112,4 +131,7 @@ def compare_json(self, pre_file, post_file): post_data = json.load(fpost) self.compare_all_pres_with_posts(pre_data, post_data) - return self.big_compare + if not inverse: + return self.big_compare + else: + return self.big_constant diff --git a/candore/modules/report.py b/candore/modules/report.py index 00c3d75..ea14725 100644 --- a/candore/modules/report.py +++ b/candore/modules/report.py @@ -17,12 +17,13 @@ def __init__(self, results): """ self.results = results - def generate_report(self, output_file, output_type): + def generate_report(self, output_file, output_type, inverse): """Generate a report of the compared results Args: output_file (str): The file to write the report to output_type (str): The type of report to generate json / CSV + inverse (bool): Shows what not changed in comparison results Returns: None Raises: @@ -31,9 +32,9 @@ def generate_report(self, output_file, output_type): if output_type == "json": self._generate_json_report(output_file) elif output_type == "html": - self._generate_html_report() + print('The HTML reporting is not implemented yet! Try next time!') elif output_type == "csv": - self._generate_csv_report(output_file) + self._generate_csv_report(output_file, inverse=inverse) else: raise ValueError("Output type {} not supported".format(output_type)) @@ -65,7 +66,7 @@ def _generate_html_report(self): # render_webpage() print("HTML report is ready to view at: http://localhost:5000") - def _generate_csv_report(self, output_file): + def _generate_csv_report(self, output_file, inverse): """Generate a CSV report of the compared results Args: @@ -78,8 +79,11 @@ def _generate_csv_report(self, output_file): output_file = Path(output_file) # Convert json to csv and write to output file csv_writer = csv.writer(output_file.open("w")) - csv_writer.writerow(["Variation Path", "Pre-Upgrade", "Post-Upgrade", "Variation"]) + # Table Column Names + columns = ["Path", "Pre-Upgrade", "Post-Upgrade", "Variation?" if not inverse else 'Constant?'] + csv_writer.writerow(columns) + # Writing Rows for var_path, vals in self.results.items(): - csv_writer.writerow([var_path, vals["pre"], vals["post"], vals["variation"]]) + csv_writer.writerow([var_path, vals["pre"], vals["post"], vals.get('variation', None)]) print("Wrote CSV report to {}".format(output_file)) print("CSV report contains {} results".format(len(self.results))) diff --git a/candore/utils.py b/candore/utils.py index 1f9dcd0..9b759e4 100644 --- a/candore/utils.py +++ b/candore/utils.py @@ -8,3 +8,8 @@ def last_index_of_element(arr, element): if arr[i] == element: return i return -1 + + +def is_list_contains_dict(_list): + contains_dict = any(isinstance(element, dict) for element in _list) + return contains_dict