Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,47 @@
|
||||
"""Opaque, scope-bound pagination cursors."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from hashlib import sha256
|
||||
|
||||
|
||||
class InvalidCursor(ValueError):
|
||||
"""Raised for malformed, cross-query, or out-of-range cursors."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Cursor:
|
||||
offset: int
|
||||
fingerprint: str
|
||||
|
||||
|
||||
def _fingerprint(*parts: str) -> str:
|
||||
return sha256("\x1f".join(parts).encode("utf-8")).hexdigest()[:32]
|
||||
|
||||
|
||||
def encode_cursor(offset: int, *scope: str) -> str:
|
||||
if offset < 0:
|
||||
raise ValueError("cursor offset must be non-negative")
|
||||
payload = {"o": offset, "f": _fingerprint(*scope)}
|
||||
raw = json.dumps(payload, separators=(",", ":"), sort_keys=True).encode("ascii")
|
||||
return base64.urlsafe_b64encode(raw).decode("ascii").rstrip("=")
|
||||
|
||||
|
||||
def decode_cursor(value: str | None, *scope: str) -> Cursor:
|
||||
if value is None:
|
||||
return Cursor(0, _fingerprint(*scope))
|
||||
if not 1 <= len(value) <= 500:
|
||||
raise InvalidCursor("cursor length is invalid")
|
||||
try:
|
||||
padded = value + "=" * (-len(value) % 4)
|
||||
payload = json.loads(base64.urlsafe_b64decode(padded).decode("ascii"))
|
||||
offset = payload["o"]
|
||||
fingerprint = payload["f"]
|
||||
except (ValueError, KeyError, TypeError, json.JSONDecodeError) as exc:
|
||||
raise InvalidCursor("cursor is invalid") from exc
|
||||
if type(offset) is not int or offset < 0 or fingerprint != _fingerprint(*scope):
|
||||
raise InvalidCursor("cursor does not match this query")
|
||||
return Cursor(offset, fingerprint)
|
||||
Reference in new issue
Block a user