From 02e8d715692b0f13f0881df5ee7cb2f79cbde165 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Mon, 25 May 2026 18:40:01 +0200 Subject: [PATCH] Initial commit: WeConnect data collector Adds collect.py with CLI flags for credentials (-u/-p), credential JSON file (-c), VIN, domains (-d), interval, output file, and --list-domains. Credentials file supports domains as array or string. Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 3 + collect.py | 541 +++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + utils/jay_diff.py | 568 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1113 insertions(+) create mode 100644 .gitignore create mode 100644 collect.py create mode 100644 requirements.txt create mode 100644 utils/jay_diff.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb1e8ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +credentials/ +vehicle_data.json \ No newline at end of file diff --git a/collect.py b/collect.py new file mode 100644 index 0000000..e019516 --- /dev/null +++ b/collect.py @@ -0,0 +1,541 @@ +#!/usr/bin/env python3 +""" +WeConnect vehicle data collector. + +Periodically fetches selected domains from a VW WeConnect vehicle and +appends timestamped records to a JSON file. The JSON structure mirrors +the WeConnect domain hierarchy: domain → status-object → field. + +Usage examples +-------------- +# Credentials from env vars, all defaults: + export WC_USER=me@example.com WC_PASS=secret + python collect.py + +# Credentials from a JSON file: + python collect.py -c creds.json -i 600 -o id3.json + +# Custom domains, 10-minute interval: + python collect.py -u me@example.com -p secret \\ + -d charging,measurements,readiness -i 600 -o id3.json + +# Everything, verbose, keep last 1 000 records: + python collect.py -d all -v --max-records 1000 + +Available domains: charging, climatisation, measurements, readiness, parking, access + +Credentials file format (JSON) +------------------------------- +{ + "username": "me@example.com", + "password": "secret", + "vin": "WVWZZZE1ZMP123456", + "domains": ["charging", "measurements", "readiness"] +} +All four keys are optional; domains may also be a comma-separated string. +Explicit CLI flags and env vars take precedence. + +Sample output record +-------------------- +{ + "ts": "2026-05-25T10:05:00+00:00", + "charging": { + "chargingStatus": { "chargingState": "readyForCharging", ... }, + "batteryStatus": { "currentSOC_pct": 80, "cruisingRangeElectric_km": 295 }, + "chargingSettings": { "targetSOC_pct": 80, ... }, + "plugStatus": { "plugConnectionState": "disconnected", ... } + }, + "measurements": { + "odometerStatus": { "odometer": 12345 }, + "rangeStatus": { "electricRange": 295, "totalRange_km": 295 }, + "batteryStatus": { "temperatureBatteryMax_K": 295.15, + "temperatureBatteryMin_K": 293.15 }, + "temperatureOutsideStatus": { "temperatureOutside_C": 18.0 } + }, + "readiness": { + "readinessStatus": { + "connectionState": { "isOnline": true, "isActive": true }, + "connectionWarning": { "insufficientBatteryLevelWarning": false } + } + }, + "parking": { + "parkingPosition": { "lat": 52.123, "lon": 13.456 } + }, + "access": { + "accessStatus": { + "overallStatus": "safe", + "doors": { "frontLeft": { "lockState": "locked", "openState": "closed" } }, + "windows": { "frontLeft": { "openState": "closed" } } + } + } +} +""" + +import argparse +import json +import logging +import os +import sys +import time +from datetime import datetime, timezone +from pathlib import Path + +try: + from weconnect import weconnect +except ImportError: + print("weconnect is not installed. Run: pip install weconnect", file=sys.stderr) + sys.exit(1) + +log = logging.getLogger(__name__) + + +# ── helpers ────────────────────────────────────────────────────────────────── + +def _str(v) -> str: + """Convert an enum-like WeConnect value to a plain string.""" + return str(v.value) if hasattr(v, "value") else str(v) + + +# ── domain extractors ──────────────────────────────────────────────────────── +# Each function returns a dict whose keys are the WeConnect status-object names +# and whose values are dicts of field-name → value — mirroring the API layout. +# Returns None when the domain is absent from the API response. + +def extract_charging(vehicle) -> dict | None: + try: + d = vehicle.domains["charging"] + except KeyError: + return None + + result: dict = {} + + try: + cs = d["chargingStatus"] + result["chargingStatus"] = { + "chargingState": _str(cs.chargingState), + "chargePower_kW": cs.chargePower_kW.value, + "remainingChargingTimeToComplete_min": cs.remainingChargingTimeToComplete_min.value, + "chargeType": _str(cs.chargeType), + } + except Exception: + log.debug("chargingStatus unavailable", exc_info=True) + + try: + bs = d["batteryStatus"] + result["batteryStatus"] = { + "currentSOC_pct": bs.currentSOC_pct.value, + "cruisingRangeElectric_km": bs.cruisingRangeElectric_km.value, + } + except Exception: + log.debug("charging/batteryStatus unavailable", exc_info=True) + + try: + cfg = d["chargingSettings"] + result["chargingSettings"] = { + "targetSOC_pct": cfg.targetSOC_pct.value, + "maxChargeCurrentAC": _str(cfg.maxChargeCurrentAC), + "autoUnlockPlugWhenCharged": _str(cfg.autoUnlockPlugWhenCharged), + } + except Exception: + log.debug("chargingSettings unavailable", exc_info=True) + + try: + ps = d["plugStatus"] + result["plugStatus"] = { + "plugConnectionState": _str(ps.plugConnectionState), + "plugLockState": _str(ps.plugLockState), + "externalPower": _str(ps.externalPower), + } + except Exception: + log.debug("plugStatus unavailable", exc_info=True) + + return result or None + + +def extract_climatisation(vehicle) -> dict | None: + try: + d = vehicle.domains["climatisation"] + except KeyError: + return None + + result: dict = {} + + try: + cl = d["climatisationStatus"] + result["climatisationStatus"] = { + "climatisationState": _str(cl.climatisationState), + "remainingClimatisationTime_min": cl.remainingClimatisationTime_min.value, + } + except Exception: + log.debug("climatisationStatus unavailable", exc_info=True) + + try: + cs = d["climatisationSettings"] + result["climatisationSettings"] = { + "targetTemperature_C": cs.targetTemperature_C.value, + "unitInCar": _str(cs.unitInCar), + } + except Exception: + log.debug("climatisationSettings unavailable", exc_info=True) + + try: + wh = d["windowHeatingStatus"] + result["windowHeatingStatus"] = { + name: _str(win.windowHeatingState) + for name, win in wh.windows.items() + } + except Exception: + log.debug("windowHeatingStatus unavailable", exc_info=True) + + return result or None + + +def extract_measurements(vehicle) -> dict | None: + try: + d = vehicle.domains["measurements"] + except KeyError: + return None + + result: dict = {} + + try: + od = d["odometerStatus"] + result["odometerStatus"] = {"odometer": od.odometer.value} + except Exception: + log.debug("odometerStatus unavailable", exc_info=True) + + try: + rs = d["rangeStatus"] + result["rangeStatus"] = { + "electricRange": rs.electricRange.value, + "totalRange_km": rs.totalRange_km.value, + } + except Exception: + log.debug("rangeStatus unavailable", exc_info=True) + + # Battery temperature — reported in Kelvin by the API + try: + bs = d["temperatureBatteryStatus"] + result["temperatureBatteryStatus"] = { + "temperatureHvBatteryMax_K": float(bs.temperatureHvBatteryMax_K.value - 273.15), + "temperatureHvBatteryMin_K": float(bs.temperatureHvBatteryMin_K.value - 273.15), + } + except Exception: + log.debug("measurements/temperatureBatteryStatus (temperature) unavailable", exc_info=True) + + try: + temp = d["temperatureOutsideStatus"] + result["temperatureOutsideStatus"] = { + "temperatureOutside_C": temp.temperatureOutside_C.value, + } + except Exception: + log.debug("temperatureOutsideStatus unavailable", exc_info=True) + + return result or None + + +def extract_readiness(vehicle) -> dict | None: + try: + d = vehicle.domains["readiness"] + except KeyError: + return None + + result: dict = {} + + try: + rs = d["readinessStatus"] + conn = rs.connectionState + warn = rs.connectionWarning + result["readinessStatus"] = { + "connectionState": { + "isOnline": conn.isOnline.value, + "isActive": conn.isActive.value, + }, + "connectionWarning": { + "insufficientBatteryLevelWarning": warn.insufficientBatteryLevelWarning.value, + "insufficientBatteryLevelError": warn.insufficientBatteryLevelError.value, + }, + } + except Exception: + log.debug("readinessStatus unavailable", exc_info=True) + + return result or None + + +def extract_parking(vehicle) -> dict | None: + try: + d = vehicle.domains["parking"] + except KeyError: + return None + + result: dict = {} + + try: + pos = d["parkingPosition"] + result["parkingPosition"] = { + "lat": pos.lat.value, + "lon": pos.lon.value, + } + except Exception: + log.debug("parkingPosition unavailable", exc_info=True) + + return result or None + + +def extract_access(vehicle) -> dict | None: + try: + d = vehicle.domains["access"] + except KeyError: + return None + + result: dict = {} + + try: + acc = d["accessStatus"] + result["accessStatus"] = { + "overallStatus": _str(acc.overallStatus), + "doors": { + name: { + "lockState": _str(door.lockState), + "openState": _str(door.openState), + } + for name, door in acc.doors.items() + }, + "windows": { + name: {"openState": _str(win.openState)} + for name, win in acc.windows.items() + }, + } + except Exception: + log.debug("accessStatus unavailable", exc_info=True) + + return result or None + + +ALL_DOMAINS: dict[str, callable] = { + "charging": extract_charging, + "climatisation": extract_climatisation, + "measurements": extract_measurements, + "readiness": extract_readiness, + "parking": extract_parking, + "access": extract_access, +} + +# ── snapshot / store helpers ───────────────────────────────────────────────── + +def collect_snapshot(vehicle, domains: list[str]) -> dict: + snapshot: dict = {"ts": datetime.now(timezone.utc).isoformat()} + for domain in domains: + data = ALL_DOMAINS[domain](vehicle) + if data is not None: + snapshot[domain] = data + return snapshot + + +def load_store(path: Path, vin: str, interval: int) -> dict: + if path.exists(): + try: + data = json.loads(path.read_text()) + if isinstance(data, dict) and "records" in data: + return data + except json.JSONDecodeError: + log.warning("Output file is corrupt — starting fresh") + return { + "meta": { + "vin": vin, + "created_at": datetime.now(timezone.utc).isoformat(), + "interval_seconds": interval, + }, + "records": [], + } + + +def save_store(path: Path, store: dict, max_records: int | None) -> None: + if max_records and len(store["records"]) > max_records: + store["records"] = store["records"][-max_records:] + path.write_text(json.dumps(store, indent=2, default=str)) + + +# ── main loop ──────────────────────────────────────────────────────────────── + +def run(args: argparse.Namespace) -> None: + logging.basicConfig( + level=logging.DEBUG if args.verbose else logging.INFO, + format="%(asctime)s %(levelname)s %(message)s", + ) + + raw = [d.strip() for d in args.domains.split(",")] + domains = list(ALL_DOMAINS) if raw == ["all"] else raw + unknown = [d for d in domains if d not in ALL_DOMAINS] + if unknown: + log.error("Unknown domain(s): %s. Available: %s", unknown, list(ALL_DOMAINS)) + sys.exit(1) + + out = Path(args.output) + + log.info("Connecting to WeConnect…") + wc = weconnect.WeConnect( + username=args.username, + password=args.password, + updateAfterLogin=False, + loginOnInit=True, + ) + wc.login() + wc.update() + + vehicles = list(wc.vehicles.values()) + if not vehicles: + log.error("No vehicles found in this WeConnect account") + sys.exit(1) + + if args.vin: + vehicle = next((v for v in vehicles if v.vin.value == args.vin), None) + if not vehicle: + log.error( + "VIN %s not found. Available: %s", + args.vin, + [v.vin.value for v in vehicles], + ) + sys.exit(1) + else: + vehicle = vehicles[0] + + vin = vehicle.vin.value + log.info("Vehicle: %s", vin) + + store = load_store(out, vin, args.interval) + log.info( + "Collecting [%s] every %ds → %s (Ctrl-C to stop)", + ", ".join(domains), + args.interval, + out, + ) + + try: + while True: + try: + wc.update() + snapshot = collect_snapshot(vehicle, domains) + store["records"].append(snapshot) + save_store(out, store, args.max_records) + log.info("Record #%d saved at %s", len(store["records"]), snapshot["ts"]) + except KeyboardInterrupt: + raise + except Exception: + log.exception("Collection failed — will retry at next interval") + + time.sleep(args.interval) + + except KeyboardInterrupt: + log.info("Stopped by user. %d records in %s", len(store["records"]), out) + + +# ── CLI ────────────────────────────────────────────────────────────────────── + +def main() -> None: + p = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + creds = p.add_argument_group( + "credentials (also accepted via WC_USER / WC_PASS env vars or -c FILE)" + ) + creds.add_argument( + "-c", "--credentials", + metavar="FILE", + help=( + "JSON file with keys: username, password, vin, domains. " + "Values are overridden by explicit CLI flags and env vars." + ), + ) + creds.add_argument( + "-u", "--username", + default=os.environ.get("WC_USER"), + help="WeConnect / MyVolkswagen email", + ) + creds.add_argument( + "-p", "--password", + default=os.environ.get("WC_PASS"), + help="WeConnect password", + ) + + p.add_argument("--vin", help="Vehicle VIN (uses first vehicle when omitted)") + p.add_argument( + "-d", "--domains", + default=None, + metavar="DOMAIN[,DOMAIN…]", + help=( + "Comma-separated domains to collect, or 'all'. " + f"Available: {', '.join(ALL_DOMAINS)}. " + "Default: charging,measurements,readiness" + ), + ) + p.add_argument( + "-i", "--interval", + type=int, + default=300, + metavar="SECONDS", + help="Collection interval in seconds (default: 300)", + ) + p.add_argument( + "-o", "--output", + default="vehicle_data.json", + metavar="FILE", + help="Output JSON file (default: vehicle_data.json)", + ) + p.add_argument( + "--max-records", + type=int, + default=None, + metavar="N", + help="Keep only the last N records in the file (default: unlimited)", + ) + p.add_argument("-v", "--verbose", action="store_true", help="Enable debug logging") + p.add_argument( + "--list-domains", + action="store_true", + help="Print available domains and exit", + ) + + args = p.parse_args() + + if args.list_domains: + print("\n".join(ALL_DOMAINS)) + sys.exit(0) + + if args.credentials: + creds_path = Path(args.credentials) + if not creds_path.exists(): + p.error(f"Credentials file not found: {args.credentials}") + try: + creds_data = json.loads(creds_path.read_text()) + except json.JSONDecodeError as exc: + p.error(f"Invalid JSON in credentials file: {exc}") + if args.username is None: + args.username = creds_data.get("username") + if args.password is None: + args.password = creds_data.get("password") + if args.vin is None: + args.vin = creds_data.get("vin") + if args.domains is None: + raw_domains = creds_data.get("domains") + if isinstance(raw_domains, list): + args.domains = ",".join(raw_domains) + else: + args.domains = raw_domains + + if args.domains is None: + args.domains = "charging,measurements,readiness" + + if not args.username or not args.password: + p.error( + "Username and password are required. " + "Pass them with -u/-p or set WC_USER / WC_PASS environment variables." + ) + + run(args) + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..85aef69 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +weconnect diff --git a/utils/jay_diff.py b/utils/jay_diff.py new file mode 100644 index 0000000..033fe12 --- /dev/null +++ b/utils/jay_diff.py @@ -0,0 +1,568 @@ +from copy import deepcopy + + +def jay_diff_rev(_old, _new): + 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): + __new = deepcopy(_new) + delta = jay_diff_rev(__new, _old) + 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: + _old[k] = _new[k] + 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 _new.items(): + if isinstance(v, dict): + jay_merge_delete(_old[k], _new[k]) + if len(_old[k].keys()) == 0: + del _old[k] + else: + if k in _old: + del _old[k] + + return _old + + +def jay_merge_full(_old, _diff): + res = _old + if "update" in _diff: + res = jay_merge_update(res, _diff['update']) + if "update_add" in _diff: + res = jay_merge_update(res, _diff['update_add']) + if "add" in _diff: + res = jay_merge_add(res, _diff['add']) + if "delete" in _diff: + res = jay_merge_delete(res, _diff['delete']) + + return res + +def main() -> 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': {'e': 'coke', 'f': 'coffee'}} + + 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': {'e': 'coke', 'f': 'coffee'}}} + + 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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}} + + 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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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 + # 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': {'e': 'coke', 'f': 'coffee'}}}) + 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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}}) + 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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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': {'e': 'coke', 'f': 'coffee'}}, '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__': + main()