mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-27 04:20:09 +00:00
The Python client now reaches what the Rust crate does and the server serves. Research covers every /v1/research route: record() files benchmarks, studies, runs and papers in one batch a kind; runs(), get_run() and compare() read them; grant(), revise() and retract() move consent and versions; the experiment plane and artifacts as before. Arena covers /v1/benchmark: catalog, leaderboard, claims (written with protocol and source), history, compare, presets, and runs, which queue a paid execution and are refused without confirm=True. wire holds the crate's Run, Benchmark, Measure, Study, Paper and Comparison field for field; canonical writes RFC 8785 bytes equal to the crate's (tests/vectors/rust.json, written by the crate); lineage and wal are the crate's records, and a WAL the crate wrote verifies here. capture() reads the harness commit (the diff's SHA-256 when dirty), the weights' SHA-256, host, CPU, GPU, device, dtype and packages, and apply() folds them into a run. runs(org=) sends no credential: the server answers ?org= only to a caller with no org. An org the token does not belong to is refused before the request, since the server would act in the home org. The command is hanzo-research; the native CLI owns `hanzo`.
135 lines
4.5 KiB
Python
135 lines
4.5 KiB
Python
"""RFC 8785 JSON canonicalization (JCS) and SHA-256 digests over it.
|
|
|
|
The Python peer of `canonical.rs` in the Rust crate: keys sorted by UTF-16 code
|
|
units, no whitespace, strings escaped by RFC 8785's table, numbers written as
|
|
ECMAScript writes them. Over strings, booleans, null, integers within 2**53 and
|
|
floats from 1e-6 up to 1e21 in magnitude the bytes equal the crate's
|
|
(`tests/vectors/rust.json` holds the crate's own output). Outside that range the
|
|
crate writes Rust's `Display` (`1e21` as 22 digits, `-0.0` as `-0`), which is not
|
|
RFC 8785; this module writes the RFC's `1e+21` and `0`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import enum
|
|
import math
|
|
import hashlib
|
|
import dataclasses
|
|
from typing import Any
|
|
|
|
__all__ = ["canonicalize", "digest", "number"]
|
|
|
|
_ESCAPES = {'"': '\\"', "\\": "\\\\", "\b": "\\b", "\f": "\\f", "\n": "\\n", "\r": "\\r", "\t": "\\t"}
|
|
|
|
|
|
def canonicalize(value: Any) -> bytes:
|
|
"""The RFC 8785 canonical bytes of a JSON value, a wire dataclass, or a mix of them."""
|
|
out: list[str] = []
|
|
_write(_plain(value), out)
|
|
try:
|
|
return "".join(out).encode("utf-8")
|
|
except UnicodeEncodeError as e:
|
|
raise ValueError(f"canonical JSON needs valid Unicode: {e}") from None
|
|
|
|
|
|
def digest(value: Any) -> str:
|
|
"""Lowercase hex SHA-256 of `canonicalize(value)`, as `rfc8785_digest` in the crate."""
|
|
return hashlib.sha256(canonicalize(value)).hexdigest()
|
|
|
|
|
|
def number(x: int | float) -> str:
|
|
"""A number as ECMAScript's Number.prototype.toString writes it (RFC 8785 section 3.2.2.3)."""
|
|
if isinstance(x, bool):
|
|
raise TypeError("a boolean is not a number")
|
|
if isinstance(x, int) and abs(x) <= 2**53:
|
|
return str(x)
|
|
try:
|
|
f = float(x)
|
|
except OverflowError:
|
|
raise ValueError(f"{x} is past the range of a double") from None
|
|
if not math.isfinite(f):
|
|
raise ValueError(f"{f} has no JSON form")
|
|
if f == 0:
|
|
return "0"
|
|
sign = "-" if f < 0 else ""
|
|
# repr is the shortest decimal that reads back as f, as ECMAScript's digits are
|
|
mantissa, _, exp = repr(abs(f)).partition("e")
|
|
whole, _, frac = mantissa.partition(".")
|
|
every = whole + frac
|
|
digits = every.lstrip("0")
|
|
# value = 0.<digits> * 10**n
|
|
n = len(whole) + int(exp or 0) - (len(every) - len(digits))
|
|
digits = digits.rstrip("0")
|
|
k = len(digits)
|
|
if k <= n <= 21:
|
|
return sign + digits + "0" * (n - k)
|
|
if 0 < n <= 21:
|
|
return sign + digits[:n] + "." + digits[n:]
|
|
if -6 < n <= 0:
|
|
return sign + "0." + "0" * -n + digits
|
|
e = n - 1
|
|
mark = "+" if e > 0 else "-"
|
|
head = digits if k == 1 else digits[0] + "." + digits[1:]
|
|
return f"{sign}{head}e{mark}{abs(e)}"
|
|
|
|
|
|
def _plain(value: Any) -> Any:
|
|
"""Wire objects to plain JSON values: dataclasses by their fields, enums by value."""
|
|
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
dump = getattr(value, "dump", None)
|
|
return _plain(dump() if callable(dump) else dataclasses.asdict(value))
|
|
if isinstance(value, enum.Enum):
|
|
return _plain(value.value)
|
|
if isinstance(value, dict):
|
|
return {k: _plain(v) for k, v in value.items()}
|
|
if isinstance(value, (list, tuple)):
|
|
return [_plain(v) for v in value]
|
|
return value
|
|
|
|
|
|
def _write(v: Any, out: list[str]) -> None:
|
|
if v is None:
|
|
out.append("null")
|
|
elif v is True:
|
|
out.append("true")
|
|
elif v is False:
|
|
out.append("false")
|
|
elif isinstance(v, (int, float)):
|
|
out.append(number(v))
|
|
elif isinstance(v, str):
|
|
_string(v, out)
|
|
elif isinstance(v, list):
|
|
out.append("[")
|
|
for i, x in enumerate(v):
|
|
if i:
|
|
out.append(",")
|
|
_write(x, out)
|
|
out.append("]")
|
|
elif isinstance(v, dict):
|
|
for k in v:
|
|
if not isinstance(k, str):
|
|
raise TypeError(f"a JSON object key is a string, not {type(k).__name__}")
|
|
out.append("{")
|
|
for i, k in enumerate(sorted(v, key=lambda s: s.encode("utf-16-be", "surrogatepass"))):
|
|
if i:
|
|
out.append(",")
|
|
_string(k, out)
|
|
out.append(":")
|
|
_write(v[k], out)
|
|
out.append("}")
|
|
else:
|
|
raise TypeError(f"{type(v).__name__} has no JSON form")
|
|
|
|
|
|
def _string(s: str, out: list[str]) -> None:
|
|
out.append('"')
|
|
for c in s:
|
|
e = _ESCAPES.get(c)
|
|
if e is not None:
|
|
out.append(e)
|
|
elif c < " ":
|
|
out.append(f"\\u{ord(c):04x}")
|
|
else:
|
|
out.append(c)
|
|
out.append('"')
|