refactor(signal_v2): narrow stock_client exception catch + remove dead code

Code quality review fixes:
- _cached_request: catch httpx.HTTPError instead of bare Exception
  (avoid swallowing KeyboardInterrupt / asyncio.CancelledError)
- _request_with_retry: remove unused last_exc variable + dead post-loop
  raise paths. Final sentinel raise preserved for mypy.

6 tests still pass. Deferred to Phase 7 backlog: cache concurrency
coalescing + __aenter__/__aexit__ context manager support.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 03:43:09 +09:00
parent 8469bf7ffa
commit 90235497ae

View File

@@ -84,7 +84,7 @@ class StockClient:
data = await self._request_with_retry(method, path, **kwargs)
self._cache[cache_key] = (data, time.monotonic())
return data
except Exception:
except httpx.HTTPError:
# Stale fallback: serve old cached value if exists
if cache_key in self._cache:
stale_data, stale_ts = self._cache[cache_key]
@@ -98,7 +98,6 @@ class StockClient:
async def _request_with_retry(self, method: str, path: str, **kwargs) -> dict:
url = f"{self._base_url}{path}"
headers = self._auth_headers()
last_exc: Exception | None = None
for attempt in range(_MAX_ATTEMPTS):
try:
response = await self._client.request(
@@ -111,18 +110,15 @@ class StockClient:
response.raise_for_status()
response.raise_for_status()
return response.json()
except httpx.TimeoutException as e:
last_exc = e
except httpx.TimeoutException:
if attempt < _MAX_ATTEMPTS - 1:
await asyncio.sleep(2**attempt)
continue
raise
except httpx.HTTPStatusError as e:
last_exc = e
except httpx.HTTPStatusError:
raise
if last_exc is not None:
raise last_exc
raise RuntimeError("retry exhausted")
# Unreachable: every iteration either returns or raises
raise RuntimeError("_request_with_retry exhausted loop without raising")
def _auth_headers(self) -> dict[str, str]:
return {"X-WebAI-Key": self._api_key}