state-hub/api/schemas/inbox_feed.py

69 lines
1.6 KiB
Python
Raw Normal View History

"""Inbox change-feed envelopes (docs/inbox-change-feed-contract.md, v1)."""
from __future__ import annotations
import re
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel
CONTRACT = "statehub.inbox-feed.v1"
CURSOR_VERSION = "v1"
_CURSOR_RE = re.compile(r"^(v\d+):(\d+)$")
class CursorError(ValueError):
"""Malformed cursor (400)."""
class ForeignCursorError(ValueError):
"""Cursor of another contract version (409)."""
def encode_cursor(revision: int) -> str:
return f"{CURSOR_VERSION}:{int(revision)}"
def decode_cursor(cursor: str) -> int:
match = _CURSOR_RE.match(cursor or "")
if match is None:
raise CursorError(f"malformed cursor {cursor!r}")
if match.group(1) != CURSOR_VERSION:
raise ForeignCursorError(
f"cursor {cursor!r} belongs to another contract version; re-snapshot"
)
return int(match.group(2))
class FeedChange(BaseModel):
revision: int
entity: Literal["message", "receipt", "tombstone"]
key: str
op: Literal["upsert", "delete"]
state: dict[str, Any]
class FeedPage(BaseModel):
contract: str = CONTRACT
source_instance: str | None
source_time: datetime
high_water: str
next_cursor: str
has_more: bool
compacted_before: str
changes: list[FeedChange]
class FeedSnapshot(BaseModel):
contract: str = CONTRACT
source_instance: str | None
source_time: datetime
cursor: str
compacted_before: str
messages: list[dict[str, Any]]
receipts: list[dict[str, Any]]
tombstones: list[dict[str, Any]]
count: int
content_hash: str