diff --git a/src/grid_trading/market/tencent.py b/src/grid_trading/market/tencent.py index c7b9b1e..1ae6329 100644 --- a/src/grid_trading/market/tencent.py +++ b/src/grid_trading/market/tencent.py @@ -50,10 +50,12 @@ class TencentQuoteProvider: *, endpoint: str = TENCENT_ENDPOINT, timeout: float = 5.0, + retries: int = 1, fetcher: Callable[[str, float], bytes] | None = None, ): self.endpoint = endpoint self.timeout = timeout + self.retries = retries self._fetcher = fetcher or _default_fetcher def fetch_quotes(self, instruments: Iterable[Instrument]) -> dict[str, QuoteSnapshot]: @@ -62,12 +64,18 @@ class TencentQuoteProvider: return {} query = ",".join(symbols) url = f"{self.endpoint}{query}" - try: - raw = self._fetcher(url, self.timeout) - except (OSError, HTTPError, URLError) as exc: - raise QuoteFetchError(f"Tencent quote request failed: {exc}") from exc + raw = self._fetch_with_retry(url) return {quote.code: quote for quote in parse_tencent_response(raw)} + def _fetch_with_retry(self, url: str) -> bytes: + last_error: Exception | None = None + for _ in range(self.retries + 1): + try: + return self._fetcher(url, self.timeout) + except (OSError, HTTPError, URLError) as exc: + last_error = exc + raise QuoteFetchError(f"Tencent quote request failed: {last_error}") from last_error + def _default_fetcher(url: str, timeout: float) -> bytes: with urlopen(url, timeout=timeout) as response: diff --git a/tests/test_tencent_quotes.py b/tests/test_tencent_quotes.py index f134d96..908c261 100644 --- a/tests/test_tencent_quotes.py +++ b/tests/test_tencent_quotes.py @@ -74,3 +74,25 @@ def test_provider_wraps_fetch_errors(): with pytest.raises(QuoteFetchError, match="Tencent quote request failed"): provider.fetch_quotes([Instrument(id=1, code="000001", name="平安银行")]) + + +def test_provider_retries_once_after_transient_fetch_error(): + raw = ( + 'v_sz000001="51~平安银行~000001~10.60~10.47~10.44~0~0~0~' + '0~0~0~0~0~0~0~0~0~0~0~0~0~0~0~0~0~0~0~0~~20260708161430~0.13~1.24~10.63~10.34";' + ).encode("gbk") + calls = 0 + + def flaky_fetch(url: str, timeout: float) -> bytes: + nonlocal calls + calls += 1 + if calls == 1: + raise OSError("bad gateway") + return raw + + provider = TencentQuoteProvider(fetcher=flaky_fetch, retries=1) + + quotes = provider.fetch_quotes([Instrument(id=1, code="000001", name="平安银行")]) + + assert calls == 2 + assert quotes["000001"].price == Decimal("10.60")