hackagent.reporting.http
Request parsing, errors, pages and ETags shared by every server.
Both servers parse query parameters with these functions, so a request is accepted, refused or paged identically, and refuse with the same messages.
FORMAT_VERSION
Bumped when a response format changes, so cached ETags stop matching.
SUMMARY_PAGE
Page sizes: (default, maximum) per route.
CACHE_CONTROL
Cache headers every successful GET carries, next to its ETag.
ReportError Objects
class ReportError(Exception)
A request a server refuses, as {"detail": ...} with status.
parse_uuid
def parse_uuid(value: Any) -> Optional[str]
value as a canonical lowercase UUID string, or None.
page_args
def page_args(args: Mapping[str, str], sizes: tuple[int,
int]) -> tuple[int, int]
(page, page_size); invalid values fall back, sizes are clamped.
page_link
def page_link(query: Iterable[tuple[str, str]], page: int) -> str
The link to page of the request whose query parameters are
query: ? and the parameters in request order with every page
removed and page=<page> appended, form-encoded
(:func:urllib.parse.urlencode). Both servers emit exactly this.
paginate
def paginate(
items: Sequence[Any],
count: int,
page: int,
page_size: int,
query: Iterable[tuple[str, str]] = ()) -> dict[str, Any]
The DRF page envelope, with :func:page_link links to the next and
previous pages of the request whose query parameters are query.
goal_index_arg
def goal_index_arg(args: Mapping[str, str], *,
required: bool) -> Optional[int]
goal_index as a non-negative int; raises :class:BadRequest.
scope_arg
def scope_arg(args: Mapping[str, str]) -> Optional[str]
The run_id a request is scoped to, as given (None when absent).
goal_filters
def goal_filters(args: Mapping[str, str]) -> GoalFilters
The filters of /goals; raises :class:BadRequest.
baseline_arg
def baseline_arg(args: Mapping[str, str], current: str) -> str
The baseline of /compare; raises :class:BadRequest.
selection
def selection(body: Any) -> list[str]
The ids of POST /run/delete; raises :class:BadRequest.
query_pairs
def query_pairs(args: Iterable[tuple[str, str]]) -> list[tuple[str, str]]
A request's query parameters in a canonical order, for :func:etag:
sorted by key only, so repeated keys keep their request order (routes
read a key's first value).
etag
def etag(route: str, query: Iterable[tuple[str, str]],
revisions: Iterable[tuple[str, int]], *extra: Any) -> str
The weak ETag of a response.
The SHA-256 of the compact JSON array [FORMAT_VERSION, route, [[key, value], ...], [[campaign_run_id, revision], ...], *extra] (query
pairs as :func:query_pairs orders them, revisions in the order given),
cut to 32 hex digits:
W/"<digits>". route is the name of the route ("summary-list",
"summary", "report", "matrix", "goals", "attempts",
"structure", "costs", "compare"); revisions lists every
campaign run whose data is in the response, and extra anything else
it depends on (the list's total).
etag_matches
def etag_matches(if_none_match: Optional[str], tag: str) -> bool
Whether an If-None-Match header matches tag (weak comparison).