Package: ker-dict-tools
ker_dict_toolsSmart tools to operate on Python's dicts.dict_diff(): Function to compare two dicts reporting their differences.get_value_by_path(): Get single or multiple values from a dict passing the path leading to them.set_value_by_path(): Set a value in a nested dict passing the path leading to it.The 'path' concept:I needed a smart way to access dict whose structure could change unpredictably (for example nested list of dicts) putting them in relationship with other objects.
So i developed the functions 'get_value_by_path()' and 'set_value_by_path()' that allows me to access to those values in a safe and rapid way.
Thepathis a list of values, each one pointing to a subsequential sub-level of the dict.
For example, given the dict:dct = {
    "foo": [
        {"bar":1},
        {"baz":2}
    ]
}the path to access to the value2is:[
    "foo",      # Key for outer level of the dict
    1,          # Index for list contained in the "foo" value
    "baz"       # Key for dict at index 1 of the list
]Since is designed to be used in a context where the actual dict could be partially unknown, the entry point to define a path to be used is theget_value_by_path()function, wich accepts a much more "elastic" list (allowing simple queries on the dict).get_value_by_path(dct,path,fail=False,debug=False)Allows to retrieve the value (or values) stored somewhere in thedctdict if the givenpathis correct (corresponds to the structure of the dict).
Thedctargument can be either adictor alist of dicts.
If thefailargument is passed (True), when an element in thepathdoesn't correspond to the the layer of thedctdict where is applied, aTypeErrorexception will be raised. Otherwise the function will return an empty namedtuple with0as value for.foundattribute.
If thedebugargument is passed (True), the function will log (using theloggingmodule) every operation with adebuglevel.Accepted values in thepathlist when passed toget_values_by_path()Dict layer typeAccepted ValuesList of dictsint(Index of list)dict(key:valuepair to be matched in one or more dicts inside the list)list (of dicts)(list of dicts containing a single _key:value_pair, all to be matched in one or more dict inside the list)str(key to be found among keys of dicts contained in the list)string "*"(wildcard to return all the elements in the list)Dictstr(key for the dict)string "*"(wildcard to return all items in the dict)For example:dct = [
{ "foo": [{"bar":1, "baz":2},{"bar":3, "baz":4}] },
{ "foo": [{"bar":5, "baz":6},{"bar":7, "baz":8}] },
]
path1 = [0, "foo", {"bar":1}, "baz"] 
path2 = [0, "foo", 0, "baz"]path1andpath2will lead to the value 2.
With:path3 = [0, "foo", "*", "baz"]path3will lead to the values2and4Object returned byget_value_by_path()The function will return adict_searchobject.res = get_value_by_path(myDict, myPath)

res => 'dict_search'(
    found=n, # -> number of matches
    results=[
        dict_branch(
            path={list1} # Path leading to the value #1
            value={value1} # Value #1
            ),
        dict_branch(
            path={list2} # Path leading to the value #2
            value={any} # Value #2
            ),
        ...
            
    ]
    )The function will return a 'dict_search' namedtuple, containing two attributes:.found{int}: Number of elements in the dict matching the givenpath.results{list}: List ofdict_branchnamedtuples, each one specifing thepathandvalueof for the elements matching the given path.dict_branchnamedtuple structure:.path{list}: "normalized" path leading to the value (contains only dict keys or list indexes).value{any}: value found at specifiedpathUsingget_value_by_path()to query the dictIs also possible to verify if a given value is present at a certain layer of the dict passing as path the path leading to it and specifing the value to find as last item in the path's list.
The function returns a namedtuple that specifies at index 0 (.found) the number of matches for the given path.set_value_by_path(dct,path,value,debug=False)Allows to set a givenvalueat a certain position of thedctdict specified with the givenpath.
Thepathargument must be a list ofintsorstringsaccording to the structure of the dict (such those returned in thedict_searchobject fromget_value_by_path()).diff_dict(dct1,dct2,fail=False,startPath=None)Performs a comparison fromdct1anddct2dicts.
Those two arguments must be of the same type (bot lists of dicts or simple dicts).
Returns a 'diff_results' object, containing four attributes:.compared{bool}: True if the comparison has been performed without problems..updated{list}: List of 'updated_item' namedtuple (see below) containing info about elements present in both dicts but with different values..added{list}: List of paths pointing elements found in thedct2but not in thedct1..removed{list}: List of paths pointing elements found in thedct1but not in thedct2.Theupdated_itemnamedtuple, that populates the.updatedlist, has the follwing structure:.path{list}: Path leading to updated element..old_value{any}: Old value for the element..old_type{type}: Old type for element's value..new_value{any}: New value for the element..new_type{type}: New type for element's value.