Merge branch 'lib_carconnectivity'
Migrate we_monitor from weconnect to CarConnectivity/VW connector, fix two jay_diff correctness bugs, and replace utils/jay_diff.py with the shared jaydiff package from jayfield/jaypy.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# we_monitor
|
# we_monitor
|
||||||
|
|
||||||
Periodically polls a VW WeConnect vehicle via the `weconnect` library, stores
|
Periodically polls a VW vehicle via the `carconnectivity` library, stores
|
||||||
timestamped records as JSON, and pushes live data to connected TCP clients.
|
timestamped records as JSON, and pushes live data to connected TCP clients.
|
||||||
A PyQt5 GUI client visualises the data in real time.
|
A PyQt5 GUI client visualises the data in real time.
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ server/
|
|||||||
data_model.py — domain extractors, collect_snapshot()
|
data_model.py — domain extractors, collect_snapshot()
|
||||||
network.py — TCP push server (accept loop + broadcast)
|
network.py — TCP push server (accept loop + broadcast)
|
||||||
storage_helpers.py — load / save JSON store, midnight rotation helpers
|
storage_helpers.py — load / save JSON store, midnight rotation helpers
|
||||||
we_connect.py — WeConnect login and vehicle selection
|
we_connect.py — CarConnectivity login and vehicle selection
|
||||||
log_config.py — logging setup
|
log_config.py — logging setup
|
||||||
utils/
|
utils/
|
||||||
jay_diff.py — JSON diff / merge library
|
jay_diff.py — JSON diff / merge library
|
||||||
@@ -25,7 +25,7 @@ requirements.txt
|
|||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
```
|
```
|
||||||
pip install weconnect PyQt5 pyqtgraph
|
pip install carconnectivity carconnectivity-connector-volkswagen PyQt5 pyqtgraph
|
||||||
```
|
```
|
||||||
|
|
||||||
Or use the installer which also sets up a systemd user service:
|
Or use the installer which also sets up a systemd user service:
|
||||||
@@ -38,7 +38,7 @@ Or use the installer which also sets up a systemd user service:
|
|||||||
|
|
||||||
## Collector (`collect.py`)
|
## Collector (`collect.py`)
|
||||||
|
|
||||||
Connects to WeConnect, polls selected domains at a configurable interval, and
|
Connects via CarConnectivity, polls selected domains at a configurable interval, and
|
||||||
appends timestamped records to a rotating JSON log. Simultaneously runs a TCP
|
appends timestamped records to a rotating JSON log. Simultaneously runs a TCP
|
||||||
push server so clients receive every new snapshot the moment it is collected.
|
push server so clients receive every new snapshot the moment it is collected.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ All connection and collection settings live in a single JSON file:
|
|||||||
"password": "secret"
|
"password": "secret"
|
||||||
},
|
},
|
||||||
"vin": "WVWZZZE1ZMP000000",
|
"vin": "WVWZZZE1ZMP000000",
|
||||||
"domains": ["charging", "measurements", "readiness"],
|
"domains": ["charging", "electric_drive", "connectivity"],
|
||||||
"interval_s": 300,
|
"interval_s": 300,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"port": 9999,
|
"port": 9999,
|
||||||
@@ -79,10 +79,10 @@ All connection and collection settings live in a single JSON file:
|
|||||||
|
|
||||||
| Key | Default | Description |
|
| Key | Default | Description |
|
||||||
|-----|---------|-------------|
|
|-----|---------|-------------|
|
||||||
| `credentials.username` | — | WeConnect / MyVolkswagen email |
|
| `credentials.username` | — | MyVolkswagen email |
|
||||||
| `credentials.password` | — | WeConnect password |
|
| `credentials.password` | — | MyVolkswagen password |
|
||||||
| `vin` | first vehicle | Target VIN |
|
| `vin` | first vehicle | Target VIN |
|
||||||
| `domains` | `["charging","measurements","readiness"]` | Domains to collect (list or `"all"`) |
|
| `domains` | `["charging","electric_drive","connectivity"]` | Domains to collect (list or `"all"`) |
|
||||||
| `interval_s` | `300` | Collection interval in seconds |
|
| `interval_s` | `300` | Collection interval in seconds |
|
||||||
| `host` | `"0.0.0.0"` | Push server bind address |
|
| `host` | `"0.0.0.0"` | Push server bind address |
|
||||||
| `port` | `9999` | Push server TCP port |
|
| `port` | `9999` | Push server TCP port |
|
||||||
@@ -104,12 +104,12 @@ CLI flags override the corresponding credentials-file value when given.
|
|||||||
| `--log-dir DIR` | `log_dir` | Directory for rotating log files |
|
| `--log-dir DIR` | `log_dir` | Directory for rotating log files |
|
||||||
| `--max-records N` | — | Keep only the last N records per file (default: unlimited) |
|
| `--max-records N` | — | Keep only the last N records per file (default: unlimited) |
|
||||||
| `--diff` | `diff` | Enable diff-mode on the push server |
|
| `--diff` | `diff` | Enable diff-mode on the push server |
|
||||||
| `--dry` | — | Skip WeConnect login; run push server only (useful for GUI testing) |
|
| `--dry` | — | Skip CarConnectivity login; run push server only (useful for GUI testing) |
|
||||||
| `-v` | — | Verbose / debug logging |
|
| `-v` | — | Verbose / debug logging |
|
||||||
| `--list-domains` | — | Print available domains and exit |
|
| `--list-domains` | — | Print available domains and exit |
|
||||||
|
|
||||||
**Available domains:** `charging`, `climatisation`, `measurements`,
|
**Available domains:** `charging`, `climatisation`, `electric_drive`,
|
||||||
`readiness`, `parking`, `access`
|
`connectivity`, `vehicle`, `position`, `doors`
|
||||||
|
|
||||||
### Log rotation
|
### Log rotation
|
||||||
|
|
||||||
@@ -129,10 +129,12 @@ Log files are named `YYYY_MM_DD_HH_MM_SS_<VIN>.json` and contain:
|
|||||||
{
|
{
|
||||||
"ts": "2025-05-25T10:00:00+00:00",
|
"ts": "2025-05-25T10:00:00+00:00",
|
||||||
"charging": {
|
"charging": {
|
||||||
"batteryStatus": {
|
"state": "readyForCharging",
|
||||||
"currentSOC": { "value": 80, "unit": "%" },
|
"power": { "value": 11.0, "unit": "kW" }
|
||||||
"cruisingRangeElectric": { "value": 310, "unit": "km" }
|
},
|
||||||
}
|
"electric_drive": {
|
||||||
|
"range": { "value": 310, "unit": "km" },
|
||||||
|
"battery": { "soc": { "value": 80, "unit": "%" } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -259,14 +261,14 @@ pretty-printed JSON with a running record counter.
|
|||||||
### `collect.py`
|
### `collect.py`
|
||||||
|
|
||||||
Entry point for the collector daemon. Parses CLI / credentials-file arguments,
|
Entry point for the collector daemon. Parses CLI / credentials-file arguments,
|
||||||
establishes the WeConnect session, starts the push server, and runs the main
|
establishes the CarConnectivity session, starts the push server, and runs the main
|
||||||
poll loop.
|
poll loop.
|
||||||
|
|
||||||
### `server/data_model.py`
|
### `server/data_model.py`
|
||||||
|
|
||||||
`collect_snapshot(vehicle, domains) → dict`
|
`collect_snapshot(vehicle, domains) → dict`
|
||||||
Iterates over the requested domains on a `weconnect.Vehicle` and extracts every
|
Iterates over the requested domains on a CarConnectivity vehicle object and extracts
|
||||||
`Status` attribute into a flat-ish dict keyed by domain and status-object name.
|
fields into a flat dict keyed by domain.
|
||||||
Physical values are normalised to `{"value": …, "unit": …}`. Returns a record
|
Physical values are normalised to `{"value": …, "unit": …}`. Returns a record
|
||||||
with a top-level `"ts"` ISO-8601 timestamp.
|
with a top-level `"ts"` ISO-8601 timestamp.
|
||||||
|
|
||||||
@@ -307,8 +309,8 @@ the last 24 hours, and returns them sorted by timestamp.
|
|||||||
|
|
||||||
### `server/we_connect.py`
|
### `server/we_connect.py`
|
||||||
|
|
||||||
`connect(username, password) → WeConnect`
|
`connect(username, password) → CarConnectivity`
|
||||||
Authenticates and returns a connected `WeConnect` instance.
|
Authenticates and returns a connected `CarConnectivity` instance.
|
||||||
|
|
||||||
`select_vehicle(wc, vin) → (vehicle, error)`
|
`select_vehicle(wc, vin) → (vehicle, error)`
|
||||||
Returns the target vehicle (first available if `vin` is empty) or an error string.
|
Returns the target vehicle (first available if `vin` is empty) or an error string.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
WeConnect push-server test client.
|
CarConnectivity push-server test client.
|
||||||
|
|
||||||
Connects to the collect.py TCP push server and prints each incoming
|
Connects to the collect.py TCP push server and prints each incoming
|
||||||
snapshot to stdout as pretty-printed JSON.
|
snapshot to stdout as pretty-printed JSON.
|
||||||
@@ -17,7 +17,7 @@ import json
|
|||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from utils.jay_diff import jay_merge_full
|
from jaydiff.merge import merge_full as jay_merge_full
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
|||||||
+17
-21
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
WeConnect vehicle data collector.
|
CarConnectivity vehicle data collector.
|
||||||
|
|
||||||
Periodically fetches selected domains from a VW WeConnect vehicle and
|
Periodically fetches selected domains from a VW vehicle via CarConnectivity
|
||||||
appends timestamped records to a JSON file. The JSON structure mirrors
|
and appends timestamped records to a JSON file. The JSON structure mirrors
|
||||||
the WeConnect domain hierarchy: domain → status-object → field.
|
the domain hierarchy: domain → status-object → field.
|
||||||
|
|
||||||
Usage examples
|
Usage examples
|
||||||
--------------
|
--------------
|
||||||
@@ -18,7 +18,7 @@ Usage examples
|
|||||||
# Override domains on the CLI:
|
# Override domains on the CLI:
|
||||||
python collect.py -c credentials/alex.json -d charging,measurements
|
python collect.py -c credentials/alex.json -d charging,measurements
|
||||||
|
|
||||||
Available domains: charging, climatisation, measurements, readiness, parking, access
|
Available domains: charging, climatisation, electric_drive, connectivity, vehicle, position, doors
|
||||||
|
|
||||||
Credentials file format (JSON)
|
Credentials file format (JSON)
|
||||||
-------------------------------
|
-------------------------------
|
||||||
@@ -53,10 +53,10 @@ from datetime import datetime, timezone
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from server import log_config, network, we_connect
|
from server import log_config, network, we_connect
|
||||||
from weconnect.errors import AuthentificationError
|
from carconnectivity.errors import AuthenticationError, TemporaryAuthenticationError
|
||||||
from server.data_model import ALL_DOMAINS, collect_snapshot, apply_procedural
|
from server.data_model import ALL_DOMAINS, collect_snapshot, apply_procedural
|
||||||
from server.storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store
|
from server.storage_helpers import auto_out_path, load_last_24h_records, load_store, save_store
|
||||||
from utils.jay_diff import jay_diff_full
|
from jaydiff.diff import diff_full as jay_diff_full
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -74,12 +74,12 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if args.dry:
|
if args.dry:
|
||||||
log.info("Dry-run mode — skipping WeConnect login")
|
log.info("Dry-run mode — skipping CarConnectivity login")
|
||||||
wc = None
|
wc = None
|
||||||
vehicle = None
|
vehicle = None
|
||||||
vin = args.vin or "UNKNOWN"
|
vin = args.vin or "UNKNOWN"
|
||||||
else:
|
else:
|
||||||
log.info("Connecting to WeConnect…")
|
log.info("Connecting via CarConnectivity…")
|
||||||
wc = we_connect.connect(args.username, args.password)
|
wc = we_connect.connect(args.username, args.password)
|
||||||
vehicle, err = we_connect.select_vehicle(wc, args.vin)
|
vehicle, err = we_connect.select_vehicle(wc, args.vin)
|
||||||
if err:
|
if err:
|
||||||
@@ -132,7 +132,7 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
log.info("Midnight UTC rotation → %s", out)
|
log.info("Midnight UTC rotation → %s", out)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wc.update()
|
we_connect.update(wc)
|
||||||
snapshot = collect_snapshot(vehicle, domains)
|
snapshot = collect_snapshot(vehicle, domains)
|
||||||
apply_procedural(snapshot)
|
apply_procedural(snapshot)
|
||||||
store["records"].append(snapshot)
|
store["records"].append(snapshot)
|
||||||
@@ -146,12 +146,8 @@ def run(args: argparse.Namespace) -> None:
|
|||||||
log.info("Record #%d saved at %s", len(store["records"]), snapshot["ts"])
|
log.info("Record #%d saved at %s", len(store["records"]), snapshot["ts"])
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except AuthentificationError:
|
except (AuthenticationError, TemporaryAuthenticationError):
|
||||||
log.warning("Authentication error — re-logging in before next interval")
|
log.warning("Authentication error — will retry at next interval")
|
||||||
try:
|
|
||||||
wc.login()
|
|
||||||
except Exception:
|
|
||||||
log.exception("Re-login failed")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("Collection failed — will retry at next interval")
|
log.exception("Collection failed — will retry at next interval")
|
||||||
|
|
||||||
@@ -180,12 +176,12 @@ def main() -> None:
|
|||||||
creds.add_argument(
|
creds.add_argument(
|
||||||
"-u", "--username",
|
"-u", "--username",
|
||||||
default=os.environ.get("WC_USER"),
|
default=os.environ.get("WC_USER"),
|
||||||
help="WeConnect / MyVolkswagen email (overrides credentials file)",
|
help="MyVolkswagen email (overrides credentials file)",
|
||||||
)
|
)
|
||||||
creds.add_argument(
|
creds.add_argument(
|
||||||
"-p", "--password",
|
"-p", "--password",
|
||||||
default=os.environ.get("WC_PASS"),
|
default=os.environ.get("WC_PASS"),
|
||||||
help="WeConnect password (overrides credentials file)",
|
help="MyVolkswagen password (overrides credentials file)",
|
||||||
)
|
)
|
||||||
|
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
@@ -195,7 +191,7 @@ def main() -> None:
|
|||||||
help=(
|
help=(
|
||||||
"Comma-separated domains to collect, or 'all' (overrides credentials file). "
|
"Comma-separated domains to collect, or 'all' (overrides credentials file). "
|
||||||
f"Available: {', '.join(ALL_DOMAINS)}. "
|
f"Available: {', '.join(ALL_DOMAINS)}. "
|
||||||
"Default: charging,measurements,readiness"
|
"Default: charging,electric_drive,connectivity"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
@@ -239,7 +235,7 @@ def main() -> None:
|
|||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--dry",
|
"--dry",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Skip WeConnect login and data collection; run push server only (for GUI testing)",
|
help="Skip CarConnectivity login and data collection; run push server only (for GUI testing)",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--list-domains",
|
"--list-domains",
|
||||||
@@ -277,7 +273,7 @@ def main() -> None:
|
|||||||
if isinstance(raw_domains, list):
|
if isinstance(raw_domains, list):
|
||||||
args.domains = ",".join(raw_domains)
|
args.domains = ",".join(raw_domains)
|
||||||
else:
|
else:
|
||||||
args.domains = raw_domains or "charging,measurements,readiness"
|
args.domains = raw_domains or "charging,electric_drive,connectivity"
|
||||||
|
|
||||||
if args.interval is None:
|
if args.interval is None:
|
||||||
args.interval = int(creds_data.get("interval_s", 300))
|
args.interval = int(creds_data.get("interval_s", 300))
|
||||||
|
|||||||
+14
-15
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
|
|||||||
|
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
|
|
||||||
from utils.jay_diff import jay_merge_full
|
from jaydiff.merge import merge_full as jay_merge_full
|
||||||
|
|
||||||
pg.setConfigOption("background", "#12121e")
|
pg.setConfigOption("background", "#12121e")
|
||||||
pg.setConfigOption("foreground", "#aaaacc")
|
pg.setConfigOption("foreground", "#aaaacc")
|
||||||
@@ -94,20 +94,19 @@ _SETTINGS_PATH = Path.home() / ".config" / "we_monitor" / "plot_settings.json"
|
|||||||
# Maps full dotted trace key → short display label used in chips, legends and
|
# Maps full dotted trace key → short display label used in chips, legends and
|
||||||
# cursor readout. Falls back to the last path component for unknown keys.
|
# cursor readout. Falls back to the last path component for unknown keys.
|
||||||
_TRACE_LABELS: dict[str, str] = {
|
_TRACE_LABELS: dict[str, str] = {
|
||||||
"charging.chargingStatus.chargePower": "charge power",
|
"charging.power": "charge power",
|
||||||
"charging.chargingStatus.remainingChargingTimeToComplete": "time to full",
|
"charging.remain": "time to full",
|
||||||
"charging.batteryStatus.currentSOC": "SOC",
|
"charging.settings.target_level": "target SOC",
|
||||||
"charging.batteryStatus.cruisingRangeElectric": "range",
|
"climatisation.remain": "clim. time",
|
||||||
"charging.chargingSettings.targetSOC": "target SOC",
|
"climatisation.settings.target_temperature": "target temp",
|
||||||
"climatisation.climatisationStatus.remainingClimatisationTime": "clim. time",
|
"climatisation.environment.temperature_outside": "outside temp",
|
||||||
"climatisation.climatisationSettings.targetTemperature": "target temp",
|
"electric_drive.battery.soc": "SOC",
|
||||||
"measurements.odometerStatus.odometer": "odometer",
|
"electric_drive.range": "range",
|
||||||
"measurements.rangeStatus.electricRange": "elec. range",
|
"electric_drive.range_full": "range (full)",
|
||||||
"measurements.rangeStatus.totalRange": "total range",
|
"electric_drive.battery.temperature_max": "batt. temp max",
|
||||||
"measurements.temperatureBatteryStatus.temperatureHvBatteryMax": "batt. temp max",
|
"electric_drive.battery.temperature_min": "batt. temp min",
|
||||||
"measurements.temperatureBatteryStatus.temperatureHvBatteryMin": "batt. temp min",
|
"electric_drive.odometer.odometer": "odometer",
|
||||||
"measurements.temperatureOutsideStatus.temperatureOutside": "outside temp",
|
"procedural.range_at_100": "range 100%",
|
||||||
"procedural.range_at_100": "range 100%"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -69,7 +69,7 @@ if [[ ! -f "$CREDS_FILE" ]]; then
|
|||||||
"password": "secret"
|
"password": "secret"
|
||||||
},
|
},
|
||||||
"vin": "",
|
"vin": "",
|
||||||
"domains": ["charging", "measurements", "readiness"],
|
"domains": ["charging", "electric_drive", "connectivity"],
|
||||||
"interval_s": 300,
|
"interval_s": 300,
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"port": 9999,
|
"port": 9999,
|
||||||
@@ -93,7 +93,7 @@ mkdir -p "$SYSTEMD_DIR"
|
|||||||
|
|
||||||
cat > "$SERVICE_FILE" <<EOF
|
cat > "$SERVICE_FILE" <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=WeConnect vehicle data collector
|
Description=CarConnectivity vehicle data collector
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -1,3 +1,6 @@
|
|||||||
weconnect
|
carconnectivity
|
||||||
|
carconnectivity-connector-volkswagen
|
||||||
|
jaydiff @ git+http://192.168.22.90:3001/jayfield/jaypy.git
|
||||||
PyQt5
|
PyQt5
|
||||||
pyqtgraph
|
pyqtgraph
|
||||||
|
pytest
|
||||||
+126
-151
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -6,60 +7,62 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
# ── value helpers ─────────────────────────────────────────────────────────────
|
# ── value helpers ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _str(v) -> str:
|
def _str(attr) -> str:
|
||||||
return str(v.value) if hasattr(v, "value") else str(v)
|
"""Return the string value of a CarConnectivity attribute."""
|
||||||
|
return str(attr)
|
||||||
|
|
||||||
|
|
||||||
def _phys(value, unit: str) -> dict:
|
def _phys(value, unit: str) -> dict:
|
||||||
return {"value": value, "unit": unit}
|
return {"value": value, "unit": unit}
|
||||||
|
|
||||||
|
|
||||||
|
def _remaining_min(estimated_date_reached_attr) -> float | None:
|
||||||
|
"""Compute remaining minutes from an estimated_date_reached DateAttribute."""
|
||||||
|
edr = estimated_date_reached_attr.value
|
||||||
|
if edr is None:
|
||||||
|
return None
|
||||||
|
return max(0.0, (edr - datetime.now(timezone.utc)).total_seconds() / 60)
|
||||||
|
|
||||||
|
|
||||||
# ── domain extractors ─────────────────────────────────────────────────────────
|
# ── domain extractors ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def extract_charging(vehicle) -> dict | None:
|
def extract_charging(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["charging"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cs = d["chargingStatus"]
|
ch = vehicle.charging
|
||||||
result["chargingStatus"] = {
|
result["state"] = _str(ch.state)
|
||||||
"chargingState": _str(cs.chargingState),
|
result["type"] = _str(ch.type)
|
||||||
"chargePower": _phys(cs.chargePower_kW.value, "kW"),
|
try:
|
||||||
"remainingChargingTimeToComplete": _phys(cs.remainingChargingTimeToComplete_min.value, "min"),
|
result["power"] = _phys(ch.power.value, "kW")
|
||||||
"chargeType": _str(cs.chargeType),
|
except Exception:
|
||||||
}
|
pass
|
||||||
|
try:
|
||||||
|
rem = _remaining_min(ch.estimated_date_reached)
|
||||||
|
if rem is not None:
|
||||||
|
result["remain"] = _phys(round(rem), "min")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("chargingStatus unavailable", exc_info=True)
|
log.debug("vehicle.charging unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bs = d["batteryStatus"]
|
cfg = vehicle.charging.settings
|
||||||
result["batteryStatus"] = {
|
result["settings"] = {
|
||||||
"currentSOC": _phys(bs.currentSOC_pct.value, "%"),
|
"target_level": _phys(cfg.target_level.value, "%"),
|
||||||
"cruisingRangeElectric": _phys(bs.cruisingRangeElectric_km.value, "km"),
|
"maximum_current": _phys(cfg.maximum_current.value, "A"),
|
||||||
|
"auto_unlock": _str(cfg.auto_unlock),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("charging/batteryStatus unavailable", exc_info=True)
|
log.debug("vehicle.charging.settings unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cfg = d["chargingSettings"]
|
conn = vehicle.charging.connector
|
||||||
result["chargingSettings"] = {
|
|
||||||
"targetSOC": _phys(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"] = {
|
result["plugStatus"] = {
|
||||||
"plugConnectionState": _str(ps.plugConnectionState),
|
"connection_state": _str(conn.connection_state),
|
||||||
"plugLockState": _str(ps.plugLockState),
|
"lock_state": _str(conn.lock_state),
|
||||||
"externalPower": _str(ps.externalPower),
|
"external_power": _str(conn.external_power),
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("plugStatus unavailable", exc_info=True)
|
log.debug("plugStatus unavailable", exc_info=True)
|
||||||
@@ -68,171 +71,143 @@ def extract_charging(vehicle) -> dict | None:
|
|||||||
|
|
||||||
|
|
||||||
def extract_climatisation(vehicle) -> dict | None:
|
def extract_climatisation(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["climatisation"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cl = d["climatisationStatus"]
|
cl = vehicle.climatization
|
||||||
result["climatisationStatus"] = {
|
result["state"] = _str(cl.state)
|
||||||
"climatisationState": _str(cl.climatisationState),
|
try:
|
||||||
"remainingClimatisationTime": _phys(cl.remainingClimatisationTime_min.value, "min"),
|
rem = _remaining_min(cl.estimated_date_reached)
|
||||||
}
|
if rem is not None:
|
||||||
|
result["remain"] = _phys(round(rem), "min")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("climatisationStatus unavailable", exc_info=True)
|
log.debug("vehicle.climatization unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cs = d["climatisationSettings"]
|
cs = vehicle.climatization.settings
|
||||||
result["climatisationSettings"] = {
|
result["settings"] = {
|
||||||
"targetTemperature": _phys(cs.targetTemperature_C.value, "degC"),
|
"target_temperature": _phys(cs.target_temperature.value, "degC"),
|
||||||
"unitInCar": _str(cs.unitInCar),
|
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("climatisationSettings unavailable", exc_info=True)
|
log.debug("climatization.settings unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
wh = d["windowHeatingStatus"]
|
wh = vehicle.window_heatings
|
||||||
result["windowHeatingStatus"] = {
|
result["window_heatings"] = {
|
||||||
name: _str(win.windowHeatingState)
|
name: _str(win.heating_state)
|
||||||
for name, win in wh.windows.items()
|
for name, win in wh.heatings.items()
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("windowHeatingStatus unavailable", exc_info=True)
|
log.debug("vehicle.window_heatings unavailable", exc_info=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result["environment"] = {
|
||||||
|
"temperature_outside": _phys(vehicle.outside_temperature.value, "degC"),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
log.debug("vehicle.outside_temperature unavailable", exc_info=True)
|
||||||
|
|
||||||
return result or None
|
return result or None
|
||||||
|
|
||||||
|
|
||||||
def extract_measurements(vehicle) -> dict | None:
|
def extract_electric_drive(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["measurements"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
od = d["odometerStatus"]
|
ed = vehicle.get_electric_drive()
|
||||||
result["odometerStatus"] = {"odometer": _phys(od.odometer.value, "km")}
|
result["range"] = _phys(ed.range.value, "km")
|
||||||
|
result["range_full"] = _phys(round(float(ed.range_estimated_full.value), 1), "km")
|
||||||
|
try:
|
||||||
|
bat = ed.battery
|
||||||
|
result["battery"] = {
|
||||||
|
"soc": _phys(ed.level.value, "%"),
|
||||||
|
"temperature_max": _phys(round(float(bat.temperature_max.value) - 273.15, 1), "degC"),
|
||||||
|
"temperature_min": _phys(round(float(bat.temperature_min.value) - 273.15, 1), "degC"),
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
log.debug("electric_drive/battery unavailable", exc_info=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("odometerStatus unavailable", exc_info=True)
|
log.debug("electric_drive unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rs = d["rangeStatus"]
|
result["odometer"] = {"odometer": _phys(vehicle.odometer.value, "km")}
|
||||||
result["rangeStatus"] = {
|
|
||||||
"electricRange": _phys(rs.electricRange.value, "km"),
|
|
||||||
"totalRange": _phys(rs.totalRange_km.value, "km"),
|
|
||||||
}
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("rangeStatus unavailable", exc_info=True)
|
log.debug("odometer unavailable", exc_info=True)
|
||||||
|
|
||||||
try:
|
|
||||||
bs = d["temperatureBatteryStatus"]
|
|
||||||
result["temperatureBatteryStatus"] = {
|
|
||||||
"temperatureHvBatteryMax": _phys(float(bs.temperatureHvBatteryMax_K.value - 273.15), "degC"),
|
|
||||||
"temperatureHvBatteryMin": _phys(float(bs.temperatureHvBatteryMin_K.value - 273.15), "degC"),
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
log.debug("measurements/temperatureBatteryStatus unavailable", exc_info=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
temp = d["temperatureOutsideStatus"]
|
|
||||||
result["temperatureOutsideStatus"] = {
|
|
||||||
"temperatureOutside": _phys(temp.temperatureOutside_C.value, "degC"),
|
|
||||||
}
|
|
||||||
except Exception:
|
|
||||||
log.debug("temperatureOutsideStatus unavailable", exc_info=True)
|
|
||||||
|
|
||||||
return result or None
|
return result or None
|
||||||
|
|
||||||
|
|
||||||
def extract_readiness(vehicle) -> dict | None:
|
def extract_connectivity(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["readiness"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rs = d["readinessStatus"]
|
state = str(vehicle.connection_state)
|
||||||
conn = rs.connectionState
|
result["state"] = state
|
||||||
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:
|
except Exception:
|
||||||
log.debug("readinessStatus unavailable", exc_info=True)
|
log.debug("connectivity.state unavailable", exc_info=True)
|
||||||
|
|
||||||
|
return result or None
|
||||||
|
|
||||||
|
def extract_vehicle(vehicle) -> dict | None:
|
||||||
|
result: dict = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
state = str(vehicle.state)
|
||||||
|
result["state"] = state
|
||||||
|
except Exception:
|
||||||
|
log.debug("vehicle.state unavailable", exc_info=True)
|
||||||
|
|
||||||
|
return result or None
|
||||||
|
|
||||||
|
def extract_position(vehicle) -> dict | None:
|
||||||
|
result: dict = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
pos = vehicle.position
|
||||||
|
result["lat"] = pos.latitude.value
|
||||||
|
result["lon"] = pos.longitude.value
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
log.debug("position unavailable", exc_info=True)
|
||||||
|
|
||||||
return result or None
|
return result or None
|
||||||
|
|
||||||
|
|
||||||
def extract_parking(vehicle) -> dict | None:
|
def extract_doors(vehicle) -> dict | None:
|
||||||
try:
|
|
||||||
d = vehicle.domains["parking"]
|
|
||||||
except KeyError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result: dict = {}
|
result: dict = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pos = d["parkingPosition"]
|
doors = vehicle.doors
|
||||||
result["parkingPosition"] = {
|
result["doors"] = {
|
||||||
"lat": pos.lat.value,
|
"overallState": _str(doors.lock_state),
|
||||||
"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": {
|
"doors": {
|
||||||
name: {
|
name: {
|
||||||
"lockState": _str(door.lockState),
|
"lockState": _str(door.lock_state),
|
||||||
"openState": _str(door.openState),
|
"openState": _str(door.open_state),
|
||||||
}
|
}
|
||||||
for name, door in acc.doors.items()
|
for name, door in doors.doors.items()
|
||||||
},
|
},
|
||||||
"windows": {
|
"windows": {
|
||||||
name: {"openState": _str(win.openState)}
|
name: {"openState": _str(win.open_state)}
|
||||||
for name, win in acc.windows.items()
|
for name, win in vehicle.windows.windows.items()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
log.debug("accessStatus unavailable", exc_info=True)
|
log.debug("vehicle.doors unavailable", exc_info=True)
|
||||||
|
|
||||||
return result or None
|
return result or None
|
||||||
|
|
||||||
|
|
||||||
ALL_DOMAINS: dict[str, callable] = {
|
ALL_DOMAINS: dict[str, Callable] = {
|
||||||
"charging": extract_charging,
|
"charging": extract_charging,
|
||||||
"climatisation": extract_climatisation,
|
"climatisation": extract_climatisation,
|
||||||
"measurements": extract_measurements,
|
"electric_drive": extract_electric_drive,
|
||||||
"readiness": extract_readiness,
|
"connectivity": extract_connectivity,
|
||||||
"parking": extract_parking,
|
"vehicle": extract_vehicle,
|
||||||
"access": extract_access,
|
"position": extract_position,
|
||||||
|
"doors": extract_doors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -265,8 +240,8 @@ def apply_procedural(record: dict) -> dict:
|
|||||||
"""
|
"""
|
||||||
proc: dict = {}
|
proc: dict = {}
|
||||||
|
|
||||||
range_at_soc = _get(record, "measurements", "rangeStatus", "totalRange", "value")
|
range_at_soc = _get(record, "electric_drive", "range", "value")
|
||||||
soc = _get(record, "charging", "batteryStatus", "currentSOC", "value")
|
soc = _get(record, "electric_drive", "battery", "soc", "value")
|
||||||
if range_at_soc is not None and soc:
|
if range_at_soc is not None and soc:
|
||||||
proc["range_at_100"] = _phys(round(100 * float(range_at_soc) / float(soc), 1), "km")
|
proc["range_at_100"] = _phys(round(100 * float(range_at_soc) / float(soc), 1), "km")
|
||||||
|
|
||||||
@@ -276,11 +251,11 @@ def apply_procedural(record: dict) -> dict:
|
|||||||
# format so the GUI picks up the field automatically.
|
# format so the GUI picks up the field automatically.
|
||||||
#
|
#
|
||||||
# Example — usable energy estimated from SOC and battery capacity:
|
# Example — usable energy estimated from SOC and battery capacity:
|
||||||
# soc = _get(record, "charging", "batteryStatus", "currentSOC", "value")
|
# soc = _get(record, "electric_drive", "battery", "soc", "value")
|
||||||
# if soc is not None:
|
# if soc is not None:
|
||||||
# proc["energy_stored"] = {"value": round(soc / 100 * 77.0, 1), "unit": "kWh"}
|
# proc["energy_stored"] = {"value": round(soc / 100 * 77.0, 1), "unit": "kWh"}
|
||||||
# ─────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
if proc:
|
if proc:
|
||||||
record["procedural"] = proc
|
record["procedural"] = proc
|
||||||
return record
|
return record
|
||||||
+1
-1
@@ -3,7 +3,7 @@ import logging
|
|||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from utils.jay_diff import jay_diff_full
|
from jaydiff.diff import diff_full as jay_diff_full
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
+32
-16
@@ -2,35 +2,51 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from weconnect import weconnect
|
from carconnectivity.carconnectivity import CarConnectivity
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("weconnect is not installed. Run: pip install weconnect", file=sys.stderr)
|
print(
|
||||||
|
"carconnectivity is not installed. Run: "
|
||||||
|
"pip install carconnectivity carconnectivity-connector-volkswagen",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def connect(username: str, password: str):
|
def connect(username: str, password: str) -> CarConnectivity:
|
||||||
wc = weconnect.WeConnect(
|
config = {
|
||||||
username=username,
|
"carConnectivity": {
|
||||||
password=password,
|
"connectors": [
|
||||||
updateAfterLogin=False,
|
{
|
||||||
loginOnInit=True,
|
"type": "volkswagen",
|
||||||
)
|
"config": {
|
||||||
wc.login()
|
"username": username,
|
||||||
wc.update()
|
"password": password,
|
||||||
return wc
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cc = CarConnectivity(config=config)
|
||||||
|
cc.startup()
|
||||||
|
cc.fetch_all()
|
||||||
|
return cc
|
||||||
|
|
||||||
|
|
||||||
def select_vehicle(wc, vin: str | None):
|
def select_vehicle(cc: CarConnectivity, vin: str | None):
|
||||||
"""Return (vehicle, error_message). error_message is None on success."""
|
"""Return (vehicle, error_message). error_message is None on success."""
|
||||||
vehicles = list(wc.vehicles.values())
|
vehicles = list(cc.get_garage().list_vehicles())
|
||||||
if not vehicles:
|
if not vehicles:
|
||||||
return None, "No vehicles found in this WeConnect account"
|
return None, "No vehicles found in this CarConnectivity account"
|
||||||
if vin:
|
if vin:
|
||||||
vehicle = next((v for v in vehicles if v.vin.value == vin), None)
|
vehicle = next((v for v in vehicles if v.vin.value == vin), None)
|
||||||
if not vehicle:
|
if not vehicle:
|
||||||
available = [v.vin.value for v in vehicles]
|
available = [v.vin.value for v in vehicles]
|
||||||
return None, f"VIN {vin} not found. Available: {available}"
|
return None, f"VIN {vin} not found. Available: {available}"
|
||||||
return vehicle, None
|
return vehicle, None
|
||||||
return vehicles[0], None
|
return vehicles[0], None
|
||||||
|
|
||||||
|
|
||||||
|
def update(cc: CarConnectivity) -> None:
|
||||||
|
cc.fetch_all()
|
||||||
@@ -1,579 +0,0 @@
|
|||||||
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):
|
|
||||||
_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 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
|
|
||||||
# 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': {'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()
|
|
||||||
Reference in New Issue
Block a user