diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e6d072 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +__pycache__/ +*.py[cod] +*.egg-info/ +.pytest_cache/ +dist/ +build/ +*.egg +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..0c57383 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# jay-diff + +Lightweight JSON diff / patch library for Python. + +Computes the difference between two dicts and applies it back, with fine-grained control over which kinds of changes (add, update, delete) to track. + +## Installation + +```bash +pip install . +``` + +## API + +### Diff functions + +| Function | Description | +|---|---| +| `jay_diff_add(old, new)` | Keys present in `new` but absent in `old` | +| `jay_diff_del(old, new)` | Keys present in `old` but absent in `new` | +| `jay_diff_upd(old, new)` | Keys present in both whose values changed | +| `jay_diff_upd_add(old, new)` | Combined update + add | +| `jay_diff_full(old, new, combine_upd_add=True)` | Full diff; returns a dict with `update_add`/`delete` keys (or `update`/`add`/`delete` when `combine_upd_add=False`) | + +### Merge functions + +| Function | Description | +|---|---| +| `jay_merge_add(old, delta)` | Apply an add-delta to `old` | +| `jay_merge_delete(old, delta)` | Apply a delete-delta to `old` | +| `jay_merge_update(old, delta)` | Apply an update-delta to `old` | +| `jay_merge_full(old, diff)` | Apply a full diff (output of `jay_diff_full`) to `old` | + +All operations are recursive — nested dicts are handled at every depth. + +## Usage + +```python +from diff import jay_diff_full, jay_merge_full +from copy import deepcopy + +old = {'soc': 80, 'range': 300, 'status': {'charging': True}} +new = {'soc': 75, 'range': 300, 'status': {'charging': False}, 'temp': 22} + +diff = jay_diff_full(old, new) +# {'update_add': {'soc': 75, 'status': {'charging': False}, 'temp': 22}} + +result = jay_merge_full(deepcopy(old), diff) +assert result == new +``` + +### Delete semantics + +When a key whose value is a dict is absent from `new`, the delete delta records `None` to signal "remove the entire key": + +```python +from diff import jay_diff_del + +jay_diff_del({'a': 1, 'b': {'x': 1, 'y': 2}}, {'a': 1}) +# {'b': None} +``` + +`jay_merge_full` / `jay_merge_delete` interpret `None` as "delete this key entirely". + +## Running tests + +```bash +pip install pytest +pytest +``` diff --git a/jay_diff/README.jay_diff.md b/diff/README.jay_diff.md similarity index 100% rename from jay_diff/README.jay_diff.md rename to diff/README.jay_diff.md diff --git a/jay_diff/__init__.py b/diff/__init__.py similarity index 82% rename from jay_diff/__init__.py rename to diff/__init__.py index 062ddeb..865f0e4 100644 --- a/jay_diff/__init__.py +++ b/diff/__init__.py @@ -1,4 +1,4 @@ -from jay_diff.jay_diff import ( +from diff.diff import ( jay_diff_add, jay_diff_del, jay_diff_upd, jay_diff_upd_add, jay_diff_full, jay_merge_add, jay_merge_delete, jay_merge_update, jay_merge_full, ) diff --git a/diff/diff.py b/diff/diff.py new file mode 100644 index 0000000..d5ffd22 --- /dev/null +++ b/diff/diff.py @@ -0,0 +1,333 @@ +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() diff --git a/diff/merge.py b/diff/merge.py new file mode 100644 index 0000000..6adfe6d --- /dev/null +++ b/diff/merge.py @@ -0,0 +1,262 @@ +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() diff --git a/jay_diff/jay_diff.py b/jay_diff/jay_diff.py deleted file mode 100644 index b0891e5..0000000 --- a/jay_diff/jay_diff.py +++ /dev/null @@ -1,590 +0,0 @@ -from copy import deepcopy - - -def jay_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 = jay_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 jay_diff_add(_old, _new): - __old = deepcopy(_old) - delta = jay_diff_rev(__old, _new) - return delta - - -def jay_diff_del(_old, _new): - delta = {} - for k in _old: - if k in _new: - if isinstance(_old[k], dict) and isinstance(_new[k], dict): - sub = jay_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 jay_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 = jay_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 jay_diff_upd_add(_old, _new): - return jay_diff_upd(_old, _new, add_if_not_exist=True) - - -def jay_diff_full(_old, _new, combine_upd_add=True): - diff = {} - if combine_upd_add: - res = jay_diff_upd_add(_old, _new) - if len(res.keys()): - diff['update_add'] = res - res = jay_diff_del(_old, _new) - if len(res.keys()): - diff['delete'] = res - else: - res = jay_diff_upd(_old, _new) - if len(res.keys()): - diff['update'] = res - res = jay_diff_add(_old, _new) - if len(res.keys()): - diff['add'] = res - res = jay_diff_del(_old, _new) - if len(res.keys()): - diff['delete'] = res - - return diff - - -def jay_merge_update(_old, _new): - for k, v in _new.items(): - if isinstance(v, dict): - r = jay_merge_update(_old.get(k, {}), v) - _old[k] = r - else: - if isinstance(_old, dict): - _old[k] = _new[k] - else: - _old = _new - return _old - - -def jay_merge_add(_old, _new): - for k, v in _new.items(): - if isinstance(v, dict): - r = jay_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 jay_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): - jay_merge_delete(_old[k], v) - else: - if k in _old: - del _old[k] - return _old - - -def jay_merge_full(_old, _diff): - _is_diff = False - res = _old - if "update" in _diff: - res = jay_merge_update(res, _diff['update']) - _is_diff = True - if "update_add" in _diff: - res = jay_merge_update(res, _diff['update_add']) - _is_diff = True - if "add" in _diff: - res = jay_merge_add(res, _diff['add']) - _is_diff = True - if "delete" in _diff: - res = jay_merge_delete(res, _diff['delete']) - _is_diff = True - - if not _is_diff and not _old: - res = _diff - - return res - -def _legacy_assertions() -> None: - result = jay_diff_add({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}) - assert result == {} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}) - assert result == {'d':'icecream'} - - result = jay_diff_add({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'b':'fries'} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}) - assert result == {'c': {'f': 'coffee'}} - - result = jay_diff_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'d':'icecream'} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}) - assert result == {'c':'pepsi'} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}) - assert result == {'c': None} - - result = jay_diff_del({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}} - - result = jay_diff_del({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hamburger', 'b':'fries', 'c': {'e': 'pepsi', 'f': 'coffee'}}) - assert result == {} - - result = jay_diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}) - assert result == {'a':'hotdog', 'b':'potatoes'} - - result = jay_diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}) - assert result == {'c': {'e': 'pepsi'}} - - result = jay_diff_upd({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}) - assert result == {'a':'hotdog'} - - result = jay_diff_upd({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}) - assert result == {'c':'coke'} - - result = jay_diff_upd({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'c':'coke'} - - result = jay_diff_upd({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}) - assert result == {} - - result = jay_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 = jay_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 = jay_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 = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}) - assert result == {'a':'hotdog'} - - result = jay_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 = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'potatoes'}) - assert result == {'a':'hotdog', 'b':'potatoes'} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'pepsi'}}) - assert result == {'c': {'e': 'pepsi'}} - - result = jay_diff_upd_add({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a':'hotdog', 'b':'fries'}) - assert result == {'a':'hotdog'} - - result = jay_diff_upd_add({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'd': 'icecream', 'c': 'coke'}) - assert result == {'c': 'coke', 'd': 'icecream'} - - result = jay_diff_upd_add({'a': 'hamburger', 'c': 'pepsi', 'd': 'icecream'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}) - assert result == {'b': 'fries', 'c': 'coke'} - - result = jay_diff_upd_add({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke'}}, {'c': {'f': 'coffee'}}) - assert result == {'c': {'f': 'coffee'}} - - result = jay_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 = jay_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'}} - - # jay_diff_full - combine_upd_add = False - - result = jay_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 = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update': {'c': 'coke'}} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add) - assert result == {'update': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': None}} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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: jay_diff_full - combine_upd_add = True - - result = jay_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 = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update_add': {'c': 'coke'}} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, combine_upd_add) - assert result == {'update_add': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': 'pepsi'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, combine_upd_add) - assert result == {'update_add': {'c': 'coke'}} - - result = jay_diff_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'a': 'hamburger', 'b': 'fries'}, combine_upd_add) - assert result == {'delete': {'c': None}} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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'}}} - - # Merge - result = jay_merge_add({}, {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_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 = jay_merge_add({'a': 'hamburger', 'b': 'fries', 'c': {'f': 'coffee'}}, {'d': 'icecream'}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}, 'd':'icecream'} - - result = jay_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 = jay_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 = jay_merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'f': 'coffee'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'e': 'coke'}} - - result = jay_merge_delete({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'c': {'e': 'coke'}}) - assert result == {'a':'hamburger', 'b':'fries', 'c': {'f': 'coffee'}} - - # jay_merge_full - # the no-jaydiff case - result = jay_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 = jay_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 = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog'}}) - assert result == {'a':'hotdog', 'b':'fries'} - - result = jay_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 = jay_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 = jay_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 = jay_merge_full({'a':'hamburger', 'b':'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}, 'update': {'a': 'hotdog'}}) - assert result == {'a':'hotdog', 'b':'fries'} - - result = jay_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 = jay_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: jay_merge_full - # combine_upd_add = True - - result = jay_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 = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update_add': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi', 'd': 'icecream'}, {'delete': {'d': 'icecream'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'coke'}, {'update_add': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'delete': {'c': 'pepsi'}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': 'pepsi'}, {'update_add': {'c': 'coke'}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': 'coke'} - - result = jay_merge_full({'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}, {'delete': {'c': None}}) - assert result == {'a': 'hamburger', 'b': 'fries'} - - result = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_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 = jay_merge_full({}, {'add': {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}}}) - assert result == {'a': 'hamburger', 'b': 'fries', 'c': {'e': 'coke', 'f': 'coffee'}} - - result = jay_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 = jay_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 = jay_merge_full(old, {'update':update, 'add':add, 'delete':delete}) - assert result == old - -if __name__ == '__main__': - _legacy_assertions() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e5f4b50 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[project] +name = "jay-diff" +version = "0.1.0" +description = "Lightweight JSON diff / patch library" +requires-python = ">=3.10" + +[tool.setuptools.packages.find] +include = ["jay_diff*"] \ No newline at end of file diff --git a/tests/test_jay_diff.py b/tests/test_jay_diff.py index c411d0f..050df9f 100644 --- a/tests/test_jay_diff.py +++ b/tests/test_jay_diff.py @@ -1,8 +1,8 @@ -"""Tests for jay_diff.""" +"""Tests for diff.""" import pytest from copy import deepcopy -from jay_diff import ( +from diff import ( jay_diff_add, jay_diff_del, jay_diff_upd, jay_diff_upd_add, jay_diff_full, jay_merge_add, jay_merge_delete, jay_merge_update, jay_merge_full, ) diff --git a/tests/test_jay_diff_real_data.py b/tests/test_jay_diff_real_data.py index 3c576cf..2452484 100644 --- a/tests/test_jay_diff_real_data.py +++ b/tests/test_jay_diff_real_data.py @@ -1,5 +1,5 @@ """ -Round-trip tests for jay_diff using real snapshot records from ./tests/data/. +Round-trip tests for diff using real snapshot records from ./tests/data/. For every pair of consecutive records (within a file and across adjacent files) we verify: @@ -14,7 +14,7 @@ import pathlib import pytest -from jay_diff import jay_diff_full, jay_merge_full +from diff import jay_diff_full, jay_merge_full DATA_DIR = pathlib.Path(__file__).parent / "data"