mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 22:27:13 +01:00
The three Python packages pinned ruff twice at different versions: requirements.txt at 0.16.7, .pre-commit-config.yaml at v0.15.2. Only the older one ever ran, so `select = ["ALL"]` was enforced two minor versions behind. Measured before aligning: v0.15.2 reports 0 findings, every 0.16.x reports CPY001 on each file -- 70 crdt-sync, 23 freedays, 71 gatelock, and no other rule. Resolved by adding the notice rather than ignoring the rule, so the gate keeps its teeth. Two gatelock modules sat at exactly the 250-line cap, so a one-line header tipped them to 251. Both are split along a real seam rather than shaved: _widget_fills.py colour arithmetic over LockConfig, no tkinter import _surfaces_focus.py selects among existing surfaces, never mutates them The focus split keeps _surfaces.py's documented containment (it stays the only module that creates, moves or destroys windows), and the static guard in test_recovery_invariants.py globs _surfaces*.py, so the new sibling is covered with no list to extend. widgets.py re-exports the fills helpers, which keeps gatelock.widgets the single public surface its tests import. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019kMvSMtsVMB3d2uoZ56pbp
97 lines
3.7 KiB
Python
97 lines
3.7 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki
|
|
"""Hybrid Logical Clock: a totally-ordered, monotonic per-node timestamp.
|
|
|
|
An HLC combines wall-clock time with a logical counter so that two ticks
|
|
issued by the same node in the same millisecond still get a strict order,
|
|
and two ticks from different nodes are always comparable (the node id breaks
|
|
ties), without requiring synchronized clocks across devices.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import UTC, datetime
|
|
import time
|
|
|
|
_ISO_PREFIX_LEN = 23 # len("YYYY-MM-DDTHH:MM:SS.mmm")
|
|
_EPOCH = datetime(1970, 1, 1, tzinfo=UTC)
|
|
|
|
|
|
@dataclass(frozen=True, order=True)
|
|
class Hlc:
|
|
"""A Hybrid Logical Clock value.
|
|
|
|
Ordered by ``(wall_time_ms, counter, node_id)`` tuple comparison, which
|
|
is also the order ``@dataclass(order=True)`` derives from the field
|
|
declaration order below -- two clocks from different nodes never compare
|
|
equal, since ``node_id`` is the final, always-distinguishing tiebreaker.
|
|
"""
|
|
|
|
wall_time_ms: int
|
|
counter: int
|
|
node_id: str
|
|
|
|
@staticmethod
|
|
def new_tick(
|
|
node_id: str,
|
|
previous: Hlc | None = None,
|
|
wall_time_ms: int | None = None,
|
|
) -> Hlc:
|
|
"""Return the next clock value for ``node_id``.
|
|
|
|
Args:
|
|
node_id: The issuing node's identifier.
|
|
previous: This node's last-issued clock value, if any. Passing
|
|
the previous value is what makes the clock monotonic even
|
|
when the wall clock hasn't advanced (or has gone backwards).
|
|
wall_time_ms: Override for the current wall-clock time in
|
|
milliseconds since the Unix epoch; defaults to the real
|
|
clock. Exposed for deterministic tests.
|
|
|
|
Returns:
|
|
A new ``Hlc`` strictly greater than ``previous`` (if given).
|
|
"""
|
|
now = wall_time_ms if wall_time_ms is not None else int(time.time() * 1000)
|
|
if previous is None:
|
|
return Hlc(wall_time_ms=now, counter=0, node_id=node_id)
|
|
new_wall = max(now, previous.wall_time_ms)
|
|
counter = previous.counter + 1 if new_wall == previous.wall_time_ms else 0
|
|
return Hlc(wall_time_ms=new_wall, counter=counter, node_id=node_id)
|
|
|
|
def to_str(self) -> str:
|
|
"""Serialize to a human-readable, lexicographically sortable string.
|
|
|
|
Format: ``<iso8601-millis>Z-<counter:04x>-<node_id>``. The iso8601
|
|
portion is fixed-width (always exactly ``YYYY-MM-DDTHH:MM:SS.mmm``),
|
|
which is what makes plain string comparison agree with ``Hlc``
|
|
comparison for the wall-clock component.
|
|
"""
|
|
seconds, millis = divmod(self.wall_time_ms, 1000)
|
|
dt = datetime.fromtimestamp(seconds, tz=UTC)
|
|
iso = dt.strftime("%Y-%m-%dT%H:%M:%S")
|
|
return f"{iso}.{millis:03d}Z-{self.counter:04x}-{self.node_id}"
|
|
|
|
@classmethod
|
|
def from_str(cls, text: str) -> Hlc:
|
|
"""Parse the format produced by :meth:`to_str`.
|
|
|
|
Raises:
|
|
ValueError: If ``text`` is not in the expected format.
|
|
"""
|
|
iso_part, sep, rest = text.partition("Z-")
|
|
if not sep or len(iso_part) != _ISO_PREFIX_LEN:
|
|
msg = f"not a valid Hlc string: {text!r}"
|
|
raise ValueError(msg)
|
|
counter_hex, sep, node_id = rest.partition("-")
|
|
if not sep:
|
|
msg = f"not a valid Hlc string: {text!r}"
|
|
raise ValueError(msg)
|
|
dt = datetime.strptime(iso_part, "%Y-%m-%dT%H:%M:%S.%f").replace(tzinfo=UTC)
|
|
delta = dt - _EPOCH
|
|
wall_time_ms = (
|
|
delta.days * 86_400_000 + delta.seconds * 1000 + delta.microseconds // 1000
|
|
)
|
|
return cls(
|
|
wall_time_ms=wall_time_ms, counter=int(counter_hex, 16), node_id=node_id
|
|
)
|