mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-06-16 21:06:15 +03:00
fix(data): catch http.client transport errors in StockTwits
A truncated/incomplete chunked response raises http.client exceptions (IncompleteRead/BadStatusLine) that are not OSErrors, so they bypassed the existing handler and crashed the analysis. Broaden the catch so the fetch degrades to its placeholder string like every other transport failure.
This commit is contained in:
42
tests/test_stocktwits_resilience.py
Normal file
42
tests/test_stocktwits_resilience.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""StockTwits fetch degrades (never raises) on transport errors, including the
|
||||
http.client chunked-transfer exceptions that are not OSErrors (#1024)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import http.client
|
||||
from unittest.mock import patch
|
||||
from urllib.error import HTTPError
|
||||
|
||||
import pytest
|
||||
|
||||
from tradingagents.dataflows import stocktwits
|
||||
|
||||
|
||||
def _raise(exc):
|
||||
class _Resp:
|
||||
def __enter__(self_inner):
|
||||
return self_inner
|
||||
|
||||
def __exit__(self_inner, *a):
|
||||
return False
|
||||
|
||||
def read(self_inner):
|
||||
raise exc
|
||||
return _Resp()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class StockTwitsResilienceTests:
|
||||
@pytest.mark.parametrize(
|
||||
"exc",
|
||||
[
|
||||
http.client.IncompleteRead(b""),
|
||||
HTTPError("url", 503, "down", {}, None),
|
||||
TimeoutError("slow"),
|
||||
],
|
||||
)
|
||||
def test_transport_errors_return_placeholder(self, exc):
|
||||
with patch.object(stocktwits, "urlopen", return_value=_raise(exc)):
|
||||
out = stocktwits.fetch_stocktwits_messages("NVDA")
|
||||
assert "unavailable" in out.lower()
|
||||
assert out.startswith("<stocktwits unavailable")
|
||||
@@ -14,11 +14,9 @@ network call succeeded.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import http.client
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -40,7 +38,9 @@ def fetch_stocktwits_messages(ticker: str, limit: int = 30, timeout: float = 10.
|
||||
try:
|
||||
with urlopen(req, timeout=timeout) as resp:
|
||||
data = json.loads(resp.read())
|
||||
except (HTTPError, URLError, json.JSONDecodeError, TimeoutError) as exc:
|
||||
except (OSError, http.client.HTTPException, json.JSONDecodeError) as exc:
|
||||
# OSError covers URLError/TimeoutError/connection resets; HTTPException
|
||||
# covers chunked-transfer errors (IncompleteRead/BadStatusLine, #1024).
|
||||
logger.warning("StockTwits fetch failed for %s: %s", ticker, exc)
|
||||
return f"<stocktwits unavailable: {type(exc).__name__}>"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user