SaarthiAI / app /netutil.py
parthmax24's picture
working proto 5
8b96826
Raw
History Blame Contribute Delete
2.23 kB
"""HTTP helpers: secret scrubbing, friendly error text, retry-on-429.
Every error string that can reach a log line or the browser must pass
through scrub_secrets()/friendly_error() β€” requests includes the full
request URL (with ?key=...) in HTTPError messages.
"""
import re
import time
import requests
_SECRET_PATTERN = re.compile(r"((?:api[_-]?key|apikey|key)=)[^&\s]+", re.IGNORECASE)
def scrub_secrets(text):
"""Mask API keys in URLs/error messages: key=abc123 -> key=***"""
return _SECRET_PATTERN.sub(r"\1***", str(text))
def friendly_error(error):
"""Short, key-free, human-readable message for the UI."""
text = str(error)
if "429" in text:
return "Traffic API rate limit hit β€” wait ~30 seconds and try again."
if "401" in text or "403" in text:
return "A data API rejected its key β€” check the keys in .env."
if "timeout" in text.lower() or "timed out" in text.lower():
return "Network timeout while calling a data API β€” try again."
return scrub_secrets(text)
def get_with_retry(url, params=None, timeout=20, retries=3, backoff=1.5):
"""GET with automatic retry on 429/5xx. Other 4xx raise immediately."""
last_error = None
for attempt in range(retries):
try:
response = requests.get(url, params=params, timeout=timeout)
if response.status_code == 429:
last_error = requests.HTTPError(
f"429 Too Many Requests (attempt {attempt + 1}/{retries})",
response=response,
)
time.sleep(backoff * (attempt + 1))
continue
response.raise_for_status()
return response
except requests.HTTPError as error:
status = getattr(error.response, "status_code", None)
if status is not None and 400 <= status < 500 and status != 429:
raise # genuine client error β€” retrying won't help
last_error = error
time.sleep(backoff * (attempt + 1))
except requests.RequestException as error: # timeouts, connection drops
last_error = error
time.sleep(backoff * (attempt + 1))
raise last_error