Files
jaypy/diff/diff.py
T
2026-05-27 13:09:17 +02:00

334 lines
17 KiB
Python

from copy import deepcopy
def diff_rev(_old, _new):
delta = {}
if not isinstance(_old, dict):
return delta
for k in _new.keys():
if k in _old:
if isinstance(_new[k], dict):
res = diff_rev(_old[k], _new[k])
if len(res.keys()):
delta[k] = res
else:
_old[k] = _new[k]
delta[k] = _new[k]
return delta
def diff_add(_old, _new):
__old = deepcopy(_old)
delta = diff_rev(__old, _new)
return delta
def diff_del(_old, _new):
delta = {}
for k in _old:
if k in _new:
if isinstance(_old[k], dict) and isinstance(_new[k], dict):
sub = diff_del(_old[k], _new[k])
if sub:
delta[k] = sub
else:
# key absent from _new: None signals "delete entire key" for dicts
delta[k] = None if isinstance(_old[k], dict) else _old[k]
return delta
def diff_upd(_old, _new, add_if_not_exist=False):
delta = {}
for k in _new.keys():
if k in _old:
if isinstance(_new[k], dict):
res = diff_upd(_old[k], _new[k], add_if_not_exist)
if len(res.keys()):
delta[k] = res
elif _new[k] != _old[k]:
delta[k] = _new[k]
elif add_if_not_exist:
delta[k] = _new[k]
return delta
def diff_upd_add(_old, _new):
return diff_upd(_old, _new, add_if_not_exist=True)
def diff_full(_old, _new, combine_upd_add=True):
diff = {}
if combine_upd_add:
res = diff_upd_add(_old, _new)
if len(res.keys()):
diff['update_add'] = res
res = diff_del(_old, _new)
if len(res.keys()):
diff['delete'] = res
else:
res = diff_upd(_old, _new)
if len(res.keys()):
diff['update'] = res
res = diff_add(_old, _new)
if len(res.keys()):
diff['add'] = res
res = diff_del(_old, _new)
if len(res.keys()):
diff['delete'] = res
return diff
def _legacy_assertions() -> None:
result = diff_add({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}
result = diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'})
assert result == {}
result = diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'})
assert result == {}
result = diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'})
assert result == {'d':'icecream'}
result = diff_add({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'})
assert result == {'b':'fries'}
result = diff_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}})
assert result == {'c': {'f': 'coffee'}}
result = diff_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}})
assert result == {}
result = diff_del({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'})
assert result == {'d':'icecream'}
result = diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'})
assert result == {}
result = diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'})
assert result == {}
result = diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'})
assert result == {'c':'pepsi'}
result = diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'})
assert result == {}
result = diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'})
assert result == {'c': None}
result = diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}}
result = diff_del({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}})
assert result == {}
result = diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'})
assert result == {'a':'hotdog', 'b':'potatoes'}
result = diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}})
assert result == {'c': {'e': 'pepsi'}}
result = diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'})
assert result == {'a':'hotdog'}
result = diff_upd({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'})
assert result == {'c':'coke'}
result = diff_upd({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'})
assert result == {'c':'coke'}
result = diff_upd({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}})
assert result == {}
result = diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}})
assert result == {'c': {'e': 'pepsi'}}
# Update/add
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'})
assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'})
assert result == {'b':'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'})
assert result == {'a':'hotdog'}
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}})
assert result == {'c': {'e': 'pepsi'}}
#####################
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'})
assert result == {'a':'hotdog', 'b':'potatoes'}
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}})
assert result == {'c': {'e': 'pepsi'}}
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'})
assert result == {'a':'hotdog'}
result = diff_upd_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'})
assert result == {'c': 'coke', 'd': 'icecream'}
result = diff_upd_add({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'})
assert result == {'b': 'fries', 'c': 'coke'}
result = diff_upd_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}})
assert result == {'c': {'f': 'coffee'}}
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}})
assert result == {'c': {'e': 'pepsi'}}
result = diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}})
assert result == {'c': {'e': 'pepsi', 'h': 'cake'}}
# diff_full
combine_upd_add = False
result = diff_full({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, combine_upd_add)
assert result == {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'update': {'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add)
assert result == {'delete': {'d': 'icecream'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}, combine_upd_add)
assert result == {'add': {'d': 'icecream'}, 'delete': {'b': 'fries'}, 'update': {'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}, combine_upd_add)
assert result == {'add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}}
result = diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add)
assert result == {'update': {'c': 'pepsi'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add)
assert result == {'delete': {'c': 'pepsi'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'update': {'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add)
assert result == {'delete': {'c': None}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, combine_upd_add)
assert result == {'delete': {'c': {'e': 'coke'}}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}, combine_upd_add)
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}, combine_upd_add)
assert result == {'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}, combine_upd_add)
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update': {'c': {'e': 'pepsi'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
assert result == {'delete': {'c': None}, 'update': {'a': 'hotdog'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
assert result == {'update': {'c': {'e': 'pepsi'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}, combine_upd_add)
assert result == {'add': {'d': 'icecream'}, 'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}, combine_upd_add)
assert result == {'add': {'c': {'g': 'tea', 'h': 'cake'}}, 'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update': {'b': 'potatoes', 'c': {'e': 'pepsi'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
assert result == {'delete': {'c': None}, 'update': {'a': 'hotdog'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
assert result == {'update': {'c': {'e': 'pepsi'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}}, combine_upd_add)
assert result == {'add': {'c': {'h': 'cake'}}, 'update': {'c': {'e': 'pepsi'}}}
# ToDo: diff_full
combine_upd_add = True
result = diff_full({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, combine_upd_add)
assert result == {'update_add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'update_add': {'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add)
assert result == {'delete': {'d': 'icecream'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}, combine_upd_add)
assert result == {'delete': {'b': 'fries'}, 'update_add': {'c': 'coke', 'd': 'icecream'}}
result = diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}, combine_upd_add)
assert result == {'update_add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}}
result = diff_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add)
assert result == {'update_add': {'c': 'pepsi'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add)
assert result == {'delete': {'c': 'pepsi'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add)
assert result == {'update_add': {'c': 'coke'}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add)
assert result == {'delete': {'c': None}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, combine_upd_add)
assert result == {'delete': {'c': {'e': 'coke'}}}
result = diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}, combine_upd_add)
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}, combine_upd_add)
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}, combine_upd_add)
assert result == {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update_add': {'c': {'e': 'pepsi'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
assert result == {'update_add': {'c': {'e': 'pepsi'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}, combine_upd_add)
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}, combine_upd_add)
assert result == {'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update_add': {'b': 'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}, combine_upd_add)
assert result == {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}, combine_upd_add)
assert result == {'update_add': {'c': {'e': 'pepsi'}}}
result = diff_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}}, combine_upd_add)
assert result == {'update_add': {'c': {'e': 'pepsi', 'h': 'cake'}}}
if __name__ == '__main__':
_legacy_assertions()