Python Requests: Reachable Token Refresh and Safe Failure Boundaries

한국어로 읽기

TECH INSIGHT / EXPLAINED

Handle transport errors, HTTP status, authentication and JSON decoding as separate boundaries. A useful wrapper must make the authentication branch reachable and must not automatically repeat order creation.

Three colleagues distinguish an authentication refresh, a rate-limit wait and response inspection.
AI-generated educational illustration. Three colleagues distinguish an authentication refresh, a rate-limit wait and response inspection.

Local execution of the actual project

These captures show the existing project UI or functions executed locally with non-private sample inputs. Each caption states the scope of the execution.

Observed results from calling the original LLMStockTrading KISClient._parse with synthetic Response objects. HTTP, business-code, and JSON-decoding failures produce different exceptions. No live KIS request, token refresh, or order was executed.
Observed results from calling the original LLMStockTrading KISClient._parse with synthetic Response objects. HTTP, business-code, and JSON-decoding failures produce different exceptions. No live KIS request, token refresh, or order was executed.

Timeout is not a total stopwatch

Requests has no default timeout. timeout=(3,10) configures connection and read inactivity limits; ten seconds is not a guarantee that the whole body finishes within ten seconds. A response that keeps delivering bytes can take longer. Use an overall operation budget in addition to per-request limits.

Inspect 401 before raising it

The example allows one refresh only for a trusted read API whose authentication contract supports that action. It handles 401 before raise_for_status, closes the first response, updates the header and tries once more. A second 401 raises normally. A 403 can mean insufficient permission and must not automatically trigger refresh.

def authenticated_get_json(session, url, token, refresh_token):
    # For a trusted read-only API whose contract permits one 401 refresh.
    for attempt in range(2):
        response = session.get(url,
            headers={'Authorization': 'Bearer ' + token},
            timeout=(3, 10), allow_redirects=False)
        try:
            if response.status_code == 401 and attempt == 0:
                needs_refresh = True
            else:
                if not 200 <= response.status_code < 300:
                    response.raise_for_status()
                    raise RuntimeError('unexpected non-success status')
                return response.json()
        finally:
            response.close()
        if needs_refresh:
            token = refresh_token()  # returns a new token, exactly once
    raise RuntimeError('unreachable')

Retry only where the operation permits it

For safe reads, selected connection failures, 429 and transient 5xx can justify bounded backoff with jitter. Honor both seconds and HTTP-date Retry-After values, and stop when the overall deadline or attempt budget is exhausted. Do not silently shorten a server-requested delay.

For a POST order, a timeout may mean accepted-but-unconfirmed. Query its documented identifier and reconcile before deciding on another submission. This example intentionally does not implement a generic retry(method, url) that would hide that distinction.

JSON success is another check

A 2xx response can contain invalid JSON or the wrong schema. Catch the decoding error where the application can respond meaningfully, then validate required keys, types and domain ranges. Do not log the first 200 characters of every body: that fragment may contain personal data or secrets. Prefer status, endpoint label, content type, body length and a sanitized error code.

Test four paths without a real server: 401→200 refreshes once; 401→401 stops; 403 never refreshes; 200 with invalid JSON propagates a decoding failure. These tests verify control flow that a successful live request never exercises.

Sources and further reading

Requests timeout behavior · HTTP semantics · Rate-limit companion

Content and examples reviewed: 2026-09-29

댓글