Handle API Errors with Python Requests
TeslaSoftware Engineer · Technical ScreenLast updated
Tests your ability to write resilient HTTP client logic in Python using requests, distinguishing between retryable and non-retryable errors, handling rate limits, and safely parsing JSON.
Resilient API Client
In vehicle telemetry and fleet management services, backend microservices frequently query external and internal REST APIs. Network conditions may be unstable, upstream services may throttle calls, and responses may occasionally return invalid payloads.
Implement a function robust_get that wraps requests.get with comprehensive error handling and retry logic.
Function Signature
import requests
from typing import Any, Dict, Optional
def robust_get(
url: str,
max_retries: int = 3,
base_backoff: float = 0.5,
timeout: float = 5.0,
session: Optional[requests.Session] = None
) -> Dict[str, Any]:
pass
Requirements
-
Transient Errors & Retries:
- Transient HTTP status codes:
500,502,503, and504. - Network-level errors:
requests.exceptions.Timeoutandrequests.exceptions.ConnectionError. - Retry up to
max_retriestimes when encountering transient errors. Use exponential backoff: sleep forbase_backoff * (2 ** attempt)seconds before attemptattempt(0-indexed).
- Transient HTTP status codes:
-
Rate Limiting (HTTP 429):
- When receiving a
429 Too Many Requestsstatus, treat it as retryable up tomax_retries. - If the response includes a
Retry-Afterheader with an integer value, sleep for that number of seconds. If the header is missing or unparseable, fall back to the exponential backoff calculation.
- When receiving a
-
Non-Retryable Errors:
- Client errors (
4xxstatus codes other than429) must abort immediately without retrying.
- Client errors (
-
JSON Parsing:
- On a successful response (
2xx), attempt to parse the body using.json(). - If
requests.exceptions.JSONDecodeError(orValueError) occurs, do not retry; return an error response indicating invalid JSON.
- On a successful response (
-
Return Format: Return a dictionary containing:
"success":bool(Trueif valid 2xx status and parsed JSON,Falseotherwise)"data":Any(parsed JSON on success,Noneon failure)"status_code":Optional[int](HTTP status code, orNoneif a network connection error prevented receiving a response)"error":Optional[str](a descriptive string ifsuccessisFalse, otherwiseNone)
Example 1
# Remote endpoint returns 503 twice, then 200 with valid JSON {"status": "ok"}
result = robust_get("https://api.example.com/telemetry", max_retries=3)
# Result:
{
"success": True,
"data": {"status": "ok"},
"status_code": 200,
"error": None
}
Example 2
# Remote endpoint returns 404 Not Found
result = robust_get("https://api.example.com/unknown", max_retries=3)
# Result:
{
"success": False,
"data": None,
"status_code": 404,
"error": "Client error: 404"
}
Hints
Hint 1
Differentiate network-level exceptions caught via try/except from HTTP-level status codes returned on a successful socket connection.
Hint 2
Inspect response.headers.get('Retry-After') on 429 responses, ensuring you parse it safely to an integer before passing it to time.sleep().
Hint 3
Remember that response.raise_for_status() raises an HTTPError, but calling it indiscriminately makes it harder to separate retryable 5xx codes from non-retryable 4xx codes.
Follow-up questions
- How would you integrate jitter into the exponential backoff to avoid the thundering herd problem?
- How can you achieve similar retry and backoff behavior using urllib3.util.Retry and requests.adapters.HTTPAdapter directly?
- How would you adapt this design for streaming responses or large file downloads where loading the entire payload into memory is unacceptable?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.