utils/crdt-sync/tool/interop_seed.py
Krzysztof kuhy Rudnicki 2005c40baa
Some checks are pending
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (3.10) (push) Waiting to run
crdt-sync tests / test (3.11) (push) Waiting to run
crdt-sync tests / test (3.12) (push) Waiting to run
palette drift / palette (push) Waiting to run
Group sync_log's 11 arguments into three bundles
BREAKING: sync_log's flat keyword arguments are replaced by SyncTarget,
LogCodec and RevisionTracking. Callers change from eleven loose keywords to
four positional arguments; every consumer repo is updated in the same pass.

Eleven parameters on one function was the last real pylint finding in the
package, and the grouping is the fix rather than a workaround: the three
bundles are the three things a tick actually needs -- where to sync, how to
serialize, and whether to track revisions -- and each documents its own
fields. SyncTarget.own_ids also moves the "this device plus its pre-migration
id" rule onto the type that owns it, instead of recomputing it inline.

Internals follow: _pull_remote_logs takes a _PullContext instead of eight
positionals, and the push half splits into _merge_peers/_push, which is what
clears the 21-locals finding.

The two duplicated in-memory fakes merge into one FakeStore in conftest, as
does the no-bulk-read wrapper around it -- the shared half was the fiddly
prefix slicing, which is exactly where two copies drift.

crdt_sync now scores a clean 10.00/10 on pylint. Two suppressions remain,
both scoped and explained at the line: asserting on a private attribute IS
the timeout test, and reversing merge_record's operands IS the commutativity
test.

259 tests, still 100% branch coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCucBhUkHBBHHbsY7bSdGV
2026-08-21 14:22:25 +02:00

90 lines
2.5 KiB
Python

"""Write the Python half of the live cross-language interop check.
``crdt_sync_dart/tool/interop_check.dart`` merges a Dart record into
``_interop/devices`` and then asserts it can also see a record written by the
*Python* client -- but nothing wrote that record, so the check could only ever
fail. This is the missing half: it pushes ``py-rec`` through the real
:class:`FirebaseSyncClient`, so the Dart tool has something to find.
The two implementations must agree byte-for-byte, or a phone running Dart
would silently not see a PC running Python. That is the failure this pair of
tools exists to catch before any real data depends on it.
Run this first, then the Dart checker::
python3 tool/interop_seed.py
dart run tool/interop_check.dart
Writes only under ``_interop/``; the Dart side deletes both devices' data on
its way out.
"""
from __future__ import annotations
import logging
import sys
from crdt_sync import (
FirebaseConfig,
Hlc,
LogCodec,
MemorySyncStateStore,
Record,
RevisionTracking,
SyncTarget,
dump_log,
firebase_client_for,
load_log,
sync_log,
)
_PATH_PREFIX = "_interop/devices"
_DEVICE_ID = "pydev"
_logger = logging.getLogger("interop-seed")
# The library's canonical serializers, deliberately -- the point of the check
# is that what the real apps write is what the other language reads, so
# reimplementing the encoding here would test the wrong thing.
def main() -> int:
"""Push the Python-side record, returning a process exit code."""
logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="%(message)s")
client = firebase_client_for("interop", config=FirebaseConfig.load())
merged = sync_log(
SyncTarget(
client=client,
device_id=_DEVICE_ID,
path_prefix=_PATH_PREFIX,
),
{
"py-rec": Record(
id="py-rec",
fields={
"value": (
"written-by-python",
Hlc(wall_time_ms=1000, counter=0, node_id="node-py"),
)
},
)
},
LogCodec(
decode=load_log,
encode=dump_log,
),
RevisionTracking(
state_store=MemorySyncStateStore(),
),
)
_logger.info("python sees records: %s", sorted(merged))
_logger.info("seeded %s; now run: dart run tool/interop_check.dart", _DEVICE_ID)
return 0
if __name__ == "__main__":
raise SystemExit(main())