Functional Weave
Code in Python

http.retry-after@1.0.0

impl/python.py

2,337 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Optional

from .dates_is_leap_year import is_leap_year  ← from dates.is-leap-year ^1.0.0 · built alongside by fune
from .time_iso_to_unix import iso_to_unix  ← from time.iso-to-unix ^1.0.0 · built alongside by fune

DAY_NAMES = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
MONTHS = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
MONTH_DAYS = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
# Nine digits is 31 years; anything longer is a broken header, not a wait.
MAX_DIGITS = 9


def _all_digits(text: str) -> bool:
    # A character range, not str.isdigit(), which accepts other scripts' digits.
    return len(text) > 0 and all("0" <= ch <= "9" for ch in text)


def _imf_fixdate(t: str) -> Optional[int]:
    """An IMF-fixdate, "Sun, 06 Nov 1994 08:49:37 GMT", to Unix seconds; None if it is not one."""
    if len(t) != 29:
        return None
    if t[0:3] not in DAY_NAMES or t[3:5] != ", " or t[7] != " " or t[11] != " " or t[16] != " ":
        return None
    if t[19] != ":" or t[22] != ":" or t[25:] != " GMT":
        return None
    if t[8:11] not in MONTHS:
        return None
    month = MONTHS.index(t[8:11])
    dd, yyyy, hh, mi, ss = t[5:7], t[12:16], t[17:19], t[20:22], t[23:25]
    if not all(_all_digits(part) for part in (dd, yyyy, hh, mi, ss)):
        return None
    year, day = int(yyyy), int(dd)
    if year < 1 or int(hh) > 23 or int(mi) > 59 or int(ss) > 59:
        return None
    month_days = 29 if month == 1 and is_leap_year(year) else MONTH_DAYS[month]
    if day < 1 or day > month_days:
        return None
    return iso_to_unix("%s-%02d-%sT%s:%s:%sZ" % (yyyy, month + 1, dd, hh, mi, ss))


def retry_after_seconds(header: Optional[str], now: int) -> Optional[int]:
    """How long a client should wait before retrying, from a Retry-After
    header (RFC 9110 10.2.3): delay-seconds, or an HTTP date turned into
    seconds from `now`. A date in the past is 0. Anything else is None."""
    if not isinstance(now, int) or isinstance(now, bool):
        raise ValueError("now must be a whole number of seconds")
    if not isinstance(header, str):
        return None
    # HTTP's optional whitespace is space and tab only (RFC 9110 5.6.3).
    t = header.strip(" \t")
    if _all_digits(t):
        return int(t) if len(t) <= MAX_DIGITS else None
    at = _imf_fixdate(t)
    if at is None:
        return None
    return at - now if at > now else 0