animedex.api._envelope

Raw response envelope for animedex api and credential redaction.

The RawResponse envelope carries everything a caller needs to debug an HTTP exchange: the request that was actually sent (with credentials fingerprint-redacted), the redirect chain, the final response (status + headers + body bytes + decoded text), per-call timing, cache provenance, and a legacy local-rejection record for requests that never left the host.

Credential redaction follows the convention from #3 §5.0: keep the first 4 + last 4 characters + length, redact the middle. The token owner can recognise which token they are looking at (length differs, suffixes differ) but the value is unusable to anyone with only the redacted dump. Headers whose name matches a credential pattern are processed; values keeping a leading scheme word (Bearer, Basic, Token) get the scheme preserved.

RawRequest

class animedex.api._envelope.RawRequest(*, method: str, url: str, headers: Dict[str, str], body_preview: str | None = None)[source]

Bases: AnimedexModel

Snapshot of the request that was (or would have been) sent.

Stored verbatim except that credential headers are pre-redacted when this object lands in a RawResponse. Body is captured as body_preview (first 4 KiB) to keep envelope size bounded; callers needing the full request body should rely on the cache or on their own logging.

Variables:
  • method (str) – HTTP method, upper-cased.

  • url (str) – Final URL after path-join.

  • headers (dict[str, str]) – Outgoing headers; credentials are redacted.

  • body_preview (str or None) – First 4 KiB of the request body, or None.

method: str
url: str
headers: Dict[str, str]
body_preview: str | None

RawRedirectHop

class animedex.api._envelope.RawRedirectHop(*, status: int, headers: Dict[str, str], from_url: str, to_url: str, elapsed_ms: float)[source]

Bases: AnimedexModel

One hop in a 3xx redirect chain.

Variables:
  • status (int) – 301 / 302 / 303 / 307 / 308.

  • headers (dict[str, str]) – Response headers from this hop.

  • from_url (str) – URL the request was issued against.

  • to_url (str) – Location: header value.

  • elapsed_ms (float) – Elapsed time for this hop, in milliseconds.

status: int
headers: Dict[str, str]
from_url: str
to_url: str
elapsed_ms: float

RawTiming

class animedex.api._envelope.RawTiming(*, total_ms: float, rate_limit_wait_ms: float, request_ms: float)[source]

Bases: AnimedexModel

Per-call timing breakdown.

Variables:
  • total_ms (float) – Wall-clock time from animedex.api._dispatch.call() entry to return; includes everything below.

  • rate_limit_wait_ms (float) – Time spent waiting for a TokenBucket slot before the request was allowed to start.

  • request_ms (float) – Wall-clock time of the actual on-the-wire HTTP transaction including any redirect hops.

total_ms: float
rate_limit_wait_ms: float
request_ms: float

RawCacheInfo

class animedex.api._envelope.RawCacheInfo(*, hit: bool, key: str | None = None, ttl_remaining_s: int | None = None, fetched_at: datetime | None = None)[source]

Bases: AnimedexModel

Provenance of the response with respect to the local SQLite cache.

Variables:
  • hit (bool) – True when the response was reconstructed from the local cache and no live HTTP request was issued.

  • key (str or None) – (backend, signature) digest used to look up the row.

  • ttl_remaining_s (int or None) – Seconds remaining before the cached row expires; only set on a cache hit.

  • fetched_at (datetime or None) – Timestamp at which the cached row was originally fetched from the upstream.

hit: bool
key: str | None
ttl_remaining_s: int | None
fetched_at: datetime | None

RawResponse

class animedex.api._envelope.RawResponse(*, backend: str, request: RawRequest, redirects: List[RawRedirectHop] = [], status: int, response_headers: Dict[str, str], body_bytes: bytes, body_text: str | None, body_truncated_at_bytes: int | None = None, timing: RawTiming, cache: RawCacheInfo, firewall_rejected: Dict[str, str] | None = None)[source]

Bases: AnimedexModel

Full envelope a CLI render mode operates on.

Variables:
  • backend (str) – Backend identifier (e.g. "anilist").

  • request (RawRequest) – Snapshot of the request that was issued (or would have been, on a local reject).

  • redirects (list[RawRedirectHop]) – Ordered list of 3xx hops; empty when the response was direct.

  • status (int) – Final HTTP status code; 0 indicates a local rejection before the request left the host.

  • response_headers (dict[str, str]) – Response headers from the final hop; empty on local reject.

  • body_bytes (bytes) – Raw response body bytes; empty on local reject. --debug mode emits this base64- encoded.

  • body_text (str or None) – body_bytes.decode("utf-8") when valid; else None.

  • body_truncated_at_bytes (int or None) – Set only when --debug truncated the body for display.

  • timing (RawTiming) – Per-call timing breakdown.

  • cache (RawCacheInfo) – Cache provenance.

  • firewall_rejected (dict[str, str] or None) – {"reason": "...", "message": "..."} for local pre-request rejection metadata, currently unknown-backend envelopes; None otherwise.

backend: str
request: RawRequest
redirects: List[RawRedirectHop]
status: int
response_headers: Dict[str, str]
body_bytes: bytes
body_text: str | None
body_truncated_at_bytes: int | None
timing: RawTiming
cache: RawCacheInfo
firewall_rejected: Dict[str, str] | None

redact_credential_value

animedex.api._envelope.redact_credential_value(value: str) str[source]

Fingerprint-redact a single credential string.

Behaviour:

  • Length below _FINGERPRINT_MIN_LENGTH (24) → "<redacted len=N>".

  • Otherwise → "<first4>...<last4> (len=N)".

The threshold is set so a fingerprinted token has at least 16 unrevealed characters in the middle (16⁴ ≈ 65k for hex / 64⁴ ≈ 16M for base64url is brute-forceable; 16¹⁶ ≈ 10¹⁹ / 64¹⁶ ≈ 10²⁹ is not). The legitimate token owner can still match the dump back to the credential by length and suffix.

Parameters:

value (str) – The raw credential string.

Returns:

A redacted form safe to print or paste.

Return type:

str

redact_headers

animedex.api._envelope.redact_headers(headers: Dict[str, str]) Dict[str, str][source]

Return a copy of headers with credential values redacted.

A header is treated as carrying a credential when its name matches the case-insensitive regex (auth|cookie|api[_-]?key|token|secret). Cookie-style values have each key=value pair redacted independently; auth-style values preserve a leading scheme word (Bearer / Basic / Token / Digest).

Parameters:

headers (dict) – Source headers.

Returns:

New dict with the same keys and credential values redacted; non-credential headers unchanged.

Return type:

dict

selftest

animedex.api._envelope.selftest() bool[source]

Smoke-test the envelope and the redaction helpers.

Returns:

True on success.

Return type:

bool