For every external API/data-store call, enforce a consistent error-handling contract:
1) Validate inputs up front (fail fast for predictable bad requests)
2) Wrap the call in try/catch and log with traceback
logging.exception(...) (or equivalent) inside the except block so you keep the stack trace.raise to preserve the original stack.3) Retry only transient failures with status-aware logic
4) Make retry termination explicit
Self-contained example (pattern to follow):
import logging
import time
import requests
TRANSIENT_STATUS = {429, 500, 502, 503, 504}
def call_with_retries(url, headers, params, max_retries=3, backoff_seconds=1):
if params.get("from") and params.get("to") and params["from"] >= params["to"]:
logging.info("Skipping call due to invalid time window")
return None
attempt = 0
while True:
try:
resp = requests.get(url, headers=headers, params=params)
if resp.status_code in TRANSIENT_STATUS:
attempt += 1
if attempt > max_retries:
logging.error(f"Exceeded retries for transient status {resp.status_code}")
return None
time.sleep(backoff_seconds * attempt)
continue
if resp.status_code in {400, 401}:
logging.error(f"Non-retryable API error {resp.status_code}: {resp.text}")
return None
resp.raise_for_status()
return resp.json()
except Exception:
logging.exception("API call failed")
attempt += 1
if attempt > max_retries:
raise
time.sleep(backoff_seconds * attempt)
Apply this pattern consistently so failures are recoverable when appropriate, non-retryable errors don’t waste attempts, and logs/stack traces remain diagnosable.