Source code for pydoover.utils.diff

import copy
import json
from typing import Any


def maybe_load_json(data):
    """Attempt to load a JSON string, return the original data if it fails."""
    try:
        return json.loads(data)
    except json.JSONDecodeError:
        return data


[docs] def apply_diff( data: dict[str, Any], diff: dict[str, Any], do_delete: bool = True, clone: bool = True, ): """Apply a doover compatible diff to a JSON / dict object. Returns a new object with the diff applied. To modify the object in-place, pass `clone=False`. """ if clone: data = copy.deepcopy(data) if not isinstance(diff, dict) or not isinstance(data, dict): # if data is not a dict, we can't apply the diff # so we just return the diff return diff for k, v in diff.items(): if isinstance(v, dict): # print(f"applying: ({type(k)}) {k} -> ({type(v)}) {v} (data: ({type(data)}) {data})") data[k] = apply_diff(data.get(k, {}), v, do_delete=do_delete, clone=clone) elif v is None: # del data[k] if do_delete: # if do_delete is True, remove the key from the dict data.pop(k, None) else: # if do_delete is False, set the key to None data[k] = None else: data[k] = v return data
[docs] def generate_diff(old, new, do_delete: bool = True): """Generate a doover compatible diff between two JSON / dict objects. A diff will contain all keys that are different between the two objects. Any keys that are in the old object but not in the new object will be set to None if do_delete is True. """ if not isinstance(new, dict) or not isinstance(old, dict): return new diff = {} for k, v in new.items(): if isinstance(v, dict): d = generate_diff(old.get(k, {}), v, do_delete=do_delete) if d: diff[k] = generate_diff(old.get(k, {}), v, do_delete=do_delete) elif k not in old or old[k] != v: diff[k] = v for k in old.keys() - new.keys(): if do_delete: diff[k] = None return diff