fix(data): add Alpha Vantage request timeout and stop mislabeling bad keys

Alpha Vantage requests had no timeout (a stall could hang the run) and any
notice mentioning "API key" was raised as a rate limit — so an invalid/missing
key was mislabeled and silently treated as transient. Add a 30s request timeout
and classify rate-limit phrasing before key errors (rate-limit notices also
mention "API key"), surfacing a bad key as a real configuration error.
This commit is contained in:
Yijia-Xiao
2026-06-13 21:47:06 +00:00
parent a597063747
commit e4be7cc5a3
2 changed files with 77 additions and 10 deletions

View File

@@ -7,6 +7,10 @@ from io import StringIO
API_BASE_URL = "https://www.alphavantage.co/query"
# Network timeout (seconds) so a stalled Alpha Vantage request can't hang the
# CLI/agents indefinitely (#990).
REQUEST_TIMEOUT = 30
class AlphaVantageNotConfiguredError(ValueError):
"""Raised when Alpha Vantage is selected but no API key is configured.
@@ -76,22 +80,31 @@ def _make_api_request(function_name: str, params: dict) -> dict | str:
# Remove entitlement if it's None or empty
api_params.pop("entitlement", None)
response = requests.get(API_BASE_URL, params=api_params)
response = requests.get(API_BASE_URL, params=api_params, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
response_text = response.text
# Check if response is JSON (error responses are typically JSON)
# Error responses are JSON; data responses are usually CSV (or data-keyed
# JSON). A non-JSON body is normal data.
try:
response_json = json.loads(response_text)
# Check for rate limit error
if "Information" in response_json:
info_message = response_json["Information"]
if "rate limit" in info_message.lower() or "api key" in info_message.lower():
raise AlphaVantageRateLimitError(f"Alpha Vantage rate limit exceeded: {info_message}")
except json.JSONDecodeError:
# Response is not JSON (likely CSV data), which is normal
pass
return response_text
# Alpha Vantage reports problems via "Information" / "Note". Classify so a
# genuine rate limit and an invalid/missing key aren't conflated (#991):
# rate-limit phrasing is checked first because those notices also mention
# "API key" ("your API key ... 25 requests per day").
notice = response_json.get("Information") or response_json.get("Note")
if notice:
low = notice.lower()
if any(m in low for m in ("rate limit", "requests per day", "call frequency", "premium")):
raise AlphaVantageRateLimitError(f"Alpha Vantage rate limit exceeded: {notice}")
if "api key" in low or "apikey" in low:
# Reuse the existing "not configured" error so a bad key surfaces as
# a real, actionable failure rather than a mislabeled rate limit (#991).
raise AlphaVantageNotConfiguredError(f"Alpha Vantage API key invalid or missing: {notice}")
return response_text