utils/crdt-sync/crdt_sync/_syncargs.py
Krzysztof kuhy Rudnicki 9f271b0d37 Fix the cap hook in CI, and the re-exports its --all-files run exposed
The hook entry was an absolute /home/kuhy/utils path, copied from a config
written for ~/.claude where it only ever ran locally. On an Actions runner
that is exit code 127 — "No such file or directory" — so gatelock-precommit
and crdt-sync-precommit failed on every commit of this refactor while the
hook silently checked nothing. Now repo-relative, matching the sibling hooks
(`entry: scripts/check_subproject_scope.sh`): pre-commit always runs from the
git root, so it resolves the same in both places.

Fixing that made the CI configs runnable locally, and `--all-files` then found
what per-file runs had missed — ruff pruning imports that exist *for* their
re-export. `import crdt_sync` was broken three separate ways (FileSyncStateStore,
encode_key, FileCredentialStore). Each affected module now states an explicit
__all__ so the autofixer cannot take them again, and the two files that pushed
past the cap once __all__ was added are split further:

  _firebase.py 260 -> 247 + _fberrors 25
  _sync.py     257 -> 188 + _syncargs 91

Test patch targets followed the code they patch: gatelock._outputs.shutil ->
_xrandr.shutil, gatelock._arbiter.Path -> _claims.Path.

gatelock and crdt-sync both pass `pre-commit run --all-files` in full;
389 and 259 tests at 100% branch coverage; the root gate exits 0.

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

93 lines
3.0 KiB
Python

"""The three argument bundles :func:`crdt_sync.sync_log` takes.
Split from :mod:`crdt_sync._sync` for the 250-line cap. Grouping sync_log's
arguments into what/how/whether bundles is what keeps its signature readable;
the bundles themselves carry no behaviour.
Re-exported from :mod:`crdt_sync._sync`, so existing imports keep working.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Callable
from crdt_sync._log import Log
from crdt_sync._remote import RemoteStore
from crdt_sync._syncstate import SyncStateStore
_DEFAULT_FILENAME = "log.json"
@dataclass(frozen=True)
class SyncTarget:
"""Which remote, which device, and where its logs live.
Args:
client: An authenticated :class:`RemoteStore` -- a
:class:`crdt_sync.GitHubSyncClient`, a
:class:`crdt_sync.FirebaseSyncClient`, or a mirror of both.
device_id: This device's identifier; also the directory name its own
log is pushed under.
path_prefix: The directory holding one subdirectory per device
(e.g. ``"devices"``).
legacy_device_id: The id this device pushed under before migrating to
a persisted uuid. Treated as this device's own for skip-own
purposes, so its pre-migration log is not pulled back and
re-merged as a peer's. Pass ``None`` once the old path has been
reclaimed.
"""
client: RemoteStore
device_id: str
path_prefix: str
legacy_device_id: str | None = None
@property
def own_ids(self) -> frozenset[str]:
"""Every id whose pushed log belongs to this device."""
if self.legacy_device_id is None:
return frozenset({self.device_id})
return frozenset({self.device_id, self.legacy_device_id})
@dataclass(frozen=True)
class LogCodec:
"""How a log is turned into pushed text and back.
Args:
encode: Serializes a merged log for pushing.
decode: Parses a remote device's pushed text back into a log.
Raising ``ValueError``, ``KeyError``, or ``TypeError`` is treated
as a corrupt/unparsable push, and that device is skipped for
this tick rather than aborting the whole sync.
filename: The file name each device pushes its log as.
commit_message: The commit message used for this device's push.
"""
encode: Callable[[Log], str]
decode: Callable[[str], Log]
filename: str = _DEFAULT_FILENAME
commit_message: str = "crdt_sync: update log"
@dataclass(frozen=True)
class RevisionTracking:
"""Optional skip-unchanged behaviour for a tick.
Omit it entirely and the tick behaves exactly as it always did: fetch
everything, push unconditionally.
Args:
state_store: Persists what this device last pushed and last merged.
revs_path: Where revisions live; defaults to
:func:`default_revs_path`.
"""
state_store: SyncStateStore | None = None
revs_path: str | None = None