+++ /dev/null
-import argparse
-import logging
-import jsonpatch
-import json
-from jsonpathl import jsonpath
-import types
-import sys
-
-
-"""
-Library for checking differences between json files,
-allowing pre-filtering those files using a number
-of jsonpath expressions
-This library supports the automated verification
-of backup & restore use cases for applications
-Updated: 2017-04-10
-"""
-
-__author__ = "Diego Granados"
-__copyright__ = "Copyright(c) 2017, Ericsson."
-__license__ = "New-style BSD"
-__email__ = "diego.jesus.granados.lopez@ericsson.com"
-
-
-def from_path_to_jsonpatch(matchedpath):
- """Given a json path (using jsonpath notation), to a json patch (RFC 6902)
- which can be used to remove the document fragment pointed by the input path
- Note that such conversion is not formally specified anywhere, so the conversion
- rules are experimentation-based
-
- :param matchedpath: the input path (using jsonpath notation, see http://goessner.net/articles/JsonPath)
- :return: the corresponding json patch for removing the fragment
- """
-
- logging.info("starting. filter path: %s", matchedpath)
-
- # First step: path format change
- # typical input: $['ietf-yang-library:modules-state']['module'][57]
- # desired output: /ietf-yang-library:modules-state/module/57
-
- matchedpath = matchedpath.replace("$.", "/")
- matchedpath = matchedpath.replace("$['", "/")
- matchedpath = matchedpath.replace("']['", "/")
- matchedpath = matchedpath.replace("']", "/")
-
- # this one is for the $[2] pattern
- if "$[" in matchedpath and "]" in matchedpath:
- matchedpath = matchedpath.replace("$[", "/")
- matchedpath = matchedpath.replace("]", "")
-
- matchedpath = matchedpath.replace("[", "")
- matchedpath = matchedpath.replace("]", "")
- matchedpath = matchedpath.rstrip("/")
-
- # Now, for input: /ietf-yang-library:modules-state/module/57
- # desired output: [{"op":"remove","path":"/ietf-yang-library:modules-state/module/57"}]
-
- logging.info("final filter path: %s", matchedpath)
- as_patch = '[{{"op":"remove","path":"{0}"}}]'.format(matchedpath)
- logging.info("generated patch line: %s", as_patch)
- return as_patch
-
-
-def apply_filter(json_arg, filtering_line):
- """Filters a json document by removing the elements identified by a filtering pattern
-
- :param json_arg: the document to filter
- :param filtering_line: The filtering pattern. This is specified using jsonpath notation grammar
- (see http://goessner.net/articles/JsonPath/)
- :return: the filtered document
- """
-
- logging.info("apply_filter:starting. jsonPath filter=[%s]", filtering_line)
-
- res = jsonpath(json_arg, filtering_line, result_type="PATH")
- if isinstance(res, types.BooleanType) or len(res) == 0:
- logging.info("apply_filter: The prefilter [%s] matched nothing", filtering_line)
- return json_arg
- if len(res) > 1:
- raise AssertionError(
- "Bad pre-filter [%s] (returned [%d] entries, should return one at most",
- filtering_line,
- len(res),
- )
- as_json_patch = from_path_to_jsonpatch(res[0])
- logging.info("apply_filter: applying patch! resolved patch =%s", as_json_patch)
- patched_json = jsonpatch.apply_patch(json_arg, as_json_patch)
-
- logging.info("apply_filter: json after patching: %s", patched_json)
- return patched_json
-
-
-def prefilter(json_arg, initial_prefilter):
- """Performs the prefiltering of a json file
- :param json_arg: the json document to filter (as string)
- :type json_arg: str
- :param initial_prefilter: a file containing a number of filtering patterns (using jsonpath notation)
- :return: the original document, python-deserialized and having the fragments
- matched by the filtering patterns removed
- """
-
- if not initial_prefilter:
- logging.info("prefilter not found!")
- # whether it is filtered or not, return as json so it can be handled uniformly from now on
- return json.loads(json_arg)
-
- with open(initial_prefilter) as f:
- lines = f.read().splitlines()
- logging.info("prefilter:lines in prefilter file: %d ", len(lines))
- lines = filter(lambda k: not k.startswith("#"), lines)
- logging.info("prefilter:lines after removing comments: %d ", len(lines))
- json_args_as_json = json.loads(json_arg)
- for filtering_line in lines:
- json_args_as_json = apply_filter(json_args_as_json, filtering_line)
-
- return json_args_as_json
-
-
-def prefilter_json_files_then_compare(args):
- """Main function. Prefilters the input files using provided prefiltering patterns,
- then returns number of differences (and the differences themselves, when requested)
-
- :param args: Input arguments, already parsed
- :return: the number of differences (from a jsonpatch standpoint) between the input
- json files (those input files can be prefiltered using a number of patterns when
- requested)
- """
-
- logging.info("prefilter_json_files_then_compare: starting!")
- with open(args.initialFile) as f:
- json_initial = file.read(f)
- with open(args.finalFile) as f2:
- json_final = file.read(f2)
-
- patch = jsonpatch.JsonPatch.from_diff(json_initial, json_final)
- logging.info(
- "prefilter_json_files_then_compare:differences before patching: %d",
- len(list(patch)),
- )
-
- json_initial_filtered = prefilter(json_initial, args.initial_prefilter)
- json_final_filtered = prefilter(json_final, args.finalPreFilter)
-
- patch_after_filtering = jsonpatch.JsonPatch.from_diff(
- json_initial_filtered, json_final_filtered
- )
- differences_after_patching = list(patch_after_filtering)
- logging.info(
- "prefilter_json_files_then_compare: differences after patching: %d",
- len(differences_after_patching),
- )
-
- if args.printDifferences:
- for patchline in differences_after_patching:
- print(json.dumps(patchline))
-
- print(len(differences_after_patching))
- return len(differences_after_patching)
-
-
-def Json_Diff_Check_Keyword(json_before, json_after, filter_before, filter_after):
- input_argv = [
- "-i",
- json_before,
- "-f",
- json_after,
- "-ipf",
- filter_before,
- "-fpf",
- filter_after,
- "-pd",
- ]
- sys.argv[1:] = input_argv
- logging.info("starting. constructed command line: %s", sys.argv)
- return Json_Diff_Check()
-
-
-def parse_args(args):
- parser = argparse.ArgumentParser(
- description="both initial and final json files are compared for differences. "
- "The program returns 0 when the json contents are the same, or the "
- "number of"
- " differences otherwise. Both json files can be prefiltered for "
- "certain patterns"
- " before checking the differences"
- )
-
- parser.add_argument(
- "-i",
- "--initialFile",
- required="true",
- dest="initialFile",
- action="store",
- help="initial json file",
- )
- parser.add_argument(
- "-f",
- "--finalFile",
- required="true",
- dest="finalFile",
- action="store",
- help="final json file",
- )
- parser.add_argument(
- "-ipf",
- "--initial_prefilter",
- dest="initial_prefilter",
- help="File with pre-filtering patterns to apply to the initial json file before comparing",
- )
- parser.add_argument(
- "-fpf",
- "--finalPreFilter",
- dest="finalPreFilter",
- help="File with pre-filtering patterns to apply to the final json file before comparing",
- )
- parser.add_argument(
- "-pd",
- "--printDifferences",
- action="store_true",
- help="on differences found, prints the list of paths for the found differences before exitting",
- )
- parser.add_argument(
- "-v",
- "--verbose",
- dest="verbose",
- action="store_true",
- help="generate log information",
- )
- return parser.parse_args(args)
-
-
-def Json_Diff_Check():
- args = parse_args(sys.argv[1:])
-
- if hasattr(args, "verbose"):
- if args.verbose:
- logging.basicConfig(level=logging.DEBUG)
-
- if args.printDifferences:
- logging.info("(will print differences)")
-
- result = prefilter_json_files_then_compare(args)
- return result
-
-
-if __name__ == "__main__":
- Json_Diff_Check()