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

263 lines
14 KiB
Python

from copy import deepcopy
def merge_update(_old, _new):
for k, v in _new.items():
if isinstance(v, dict):
r = merge_update(_old.get(k, {}), v)
_old[k] = r
else:
if isinstance(_old, dict):
_old[k] = _new[k]
else:
_old = _new
return _old
def merge_add(_old, _new):
for k, v in _new.items():
if isinstance(v, dict):
r = merge_add(_old.get(k, {}), v)
if k in _old:
_old[k].update(r)
else:
_old[k] = r
else:
_old[k] = _new[k]
return _old
def merge_delete(_old, _new):
for k, v in list(_new.items()):
if isinstance(v, dict) and v:
if k in _old and isinstance(_old[k], dict):
merge_delete(_old[k], v)
else:
if k in _old:
del _old[k]
return _old
def merge_full(_old, _diff):
_is_diff = False
res = _old
if "update" in _diff:
res = merge_update(res, _diff['update'])
_is_diff = True
if "update_add" in _diff:
res = merge_update(res, _diff['update_add'])
_is_diff = True
if "add" in _diff:
res = merge_add(res, _diff['add'])
_is_diff = True
if "delete" in _diff:
res = merge_delete(res, _diff['delete'])
_is_diff = True
if not _is_diff and not _old:
res = _diff
return res
def _legacy_assertions() -> None:
# Merge
result = merge_add({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}
result = merge_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'d': 'icecream'})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}, 'd':'icecream'}
result = merge_add({'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, {'d': 'icecream'})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}, 'd':'icecream'}
result = merge_add({'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}, 'd': 'icecream'}, {'d': 'icecream'})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}, 'd':'icecream'}
result = merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}, 'd': 'icecream'}, {'d': 'icecream'})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}
result = merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'f': 'coffee'}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke'}}
result = merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}}
# merge_full
# the no-jaydiff case
result = merge_full({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}
# combine_upd_add = False
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update': {'c': 'pepsi'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'add': {'d': 'icecream'}, 'delete': {'b': 'fries'}, 'update': {'c': 'coke'}})
assert result == {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}})
assert result == {'c': {'f': 'coffee'}}
result = merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'add': {'b': 'fries'}, 'delete': {'d': 'icecream'}, 'update': {'c': 'coke'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update': {'c': 'pepsi'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'c': 'pepsi'}})
assert result == {'a': 'hamburger', 'b': 'fries'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update': {'c': 'coke'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}})
assert result == {'a': 'hamburger', 'b': 'fries'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke'}}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}})
assert result == {'c': {'e': 'coke'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}})
assert result == {'a':'hotdog', 'b':'potatoes'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update': {'c': {'e': 'pepsi'}}})
assert result == {'c': {'e': 'pepsi'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog'}})
assert result == {'a':'hotdog', 'b':'fries'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update': {'c': {'e': 'pepsi'}}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'d': 'icecream'}, 'delete': {'c': None}, 'update': {'a': 'hotdog', 'b': 'potatoes'}})
assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'c': {'g': 'tea', 'h': 'cake'}}, 'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update': {'b': 'potatoes', 'c': {'e': 'pepsi'}}})
assert result == {'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}, 'b':'potatoes'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog'}})
assert result == {'a':'hotdog', 'b':'fries'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update': {'c': {'e': 'pepsi'}}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'add': {'c': {'h': 'cake'}}, 'update': {'c': {'e': 'pepsi'}}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}}
# ToDo: merge_full
# combine_upd_add = True
result = merge_full({}, {'update_add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update_add': {'c': 'coke'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'b': 'fries'}, 'update_add': {'c': 'coke', 'd': 'icecream'}})
assert result == {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'update_add': {'c': {'f': 'coffee'}}, 'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}})
assert result == {'c': {'f': 'coffee'}}
result = merge_full({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}, 'update_add': {'b': 'fries', 'c': 'coke'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update_add': {'c': 'pepsi'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'c': 'pepsi'}})
assert result == {'a': 'hamburger', 'b': 'fries'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update_add': {'c': 'coke'}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}})
assert result == {'a': 'hamburger', 'b': 'fries'}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': {'e': 'coke'}}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}})
assert result == {'c': {'e': 'coke'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes'}})
assert result == {'a':'hotdog', 'b':'potatoes'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, 'update_add': {'c': {'e': 'pepsi'}}})
assert result == {'c': {'e': 'pepsi'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}})
assert result == {'a':'hotdog', 'b':'fries'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi'}}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog', 'b': 'potatoes', 'd': 'icecream'}})
assert result == {'a':'hotdog', 'b':'potatoes', 'd': 'icecream'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'a': 'hamburger', 'c': {'f': 'coffee'}}, 'update_add': {'b': 'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}})
assert result == {'b':'potatoes', 'c': {'e': 'pepsi', 'g': 'tea', 'h': 'cake'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update_add': {'a': 'hotdog'}})
assert result == {'a':'hotdog', 'b':'fries'}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi'}}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}
result = merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'update_add': {'c': {'e': 'pepsi', 'h': 'cake'}}})
assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee', 'h': 'cake'}}
result = merge_full({}, {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}
result = merge_full({}, {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black'}}}}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black'}}}}
result = merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black'}}}}, {'add': {'c': {'f': {'coffee': {'j': 'sugar'}}}}})
assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': {'coffee': {'h': 'black', 'j': 'sugar'}}}}
old = {'charging': {
'batteryStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:34Z', 'cruisingRangeElectric_km': 192}},
'chargingStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:34Z'}},
'chargingSettings': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:30Z'}},
'plugStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:29Z'}}}}
update = {'charging': {
'batteryStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:35:46Z', 'cruisingRangeElectric_km': 191}},
'chargingStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:35:46Z'}}}}
add = {'charging': {
'batteryStatus': {'value': {'currentSOC_pct': 55}}},
'fuelStatus': {'rangeStatus': {'value': {'primaryEngine': {'currentSOC_pct': 55}}}},
'measurements': {'fuelLevelStatus': {'value': {'currentSOC_pct': 55}}}}
delete = {'charging': {
'chargingSettings': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:30Z'}},
'plugStatus': {'value': {'carCapturedTimestamp': '2024-10-19T05:34:29Z'}}}}
result = merge_full(old, {'update':update, 'add':add, 'delete':delete})
assert result == old
if __name__ == '__main__':
_legacy_assertions()