You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

110 lines
3.7 KiB
Python

"""Base httpx async client for Stock Oracle."""
from __future__ import annotations
from typing import Any
import httpx
from libs.common.retries import with_retry
from libs.oracle_client.exceptions import (
OracleClientError,
OracleConnectionError,
OracleNotFoundError,
OracleServerError,
OracleTimeoutError,
)
class OracleClient:
"""Async HTTP client wrapping Stock Oracle REST API."""
def __init__(self, base_url: str, timeout: float = 30.0) -> None:
self._base_url = base_url.rstrip("/")
self._timeout = timeout
self._client: httpx.AsyncClient | None = None
async def __aenter__(self) -> OracleClient:
self._client = httpx.AsyncClient(
base_url=self._base_url,
timeout=self._timeout,
)
return self
async def __aexit__(self, *args: Any) -> None:
if self._client:
await self._client.aclose()
self._client = None
def _ensure_client(self) -> httpx.AsyncClient:
if self._client is None:
raise RuntimeError("OracleClient must be used as async context manager.")
return self._client
@with_retry(max_attempts=3, min_wait=0.1, max_wait=5.0, multiplier=0.1)
async def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
client = self._ensure_client()
try:
response = await client.get(path, params=params)
except httpx.ConnectError as exc:
raise OracleConnectionError(str(exc), source="oracle", entity=path) from exc
except httpx.TimeoutException as exc:
raise OracleTimeoutError(str(exc), source="oracle", entity=path) from exc
return self._handle_response(response, path)
@with_retry(max_attempts=3, min_wait=0.1, max_wait=5.0, multiplier=0.1)
async def post(
self,
path: str,
json: dict[str, Any] | None = None,
params: dict[str, Any] | None = None,
) -> Any:
client = self._ensure_client()
try:
response = await client.post(path, json=json, params=params)
except httpx.ConnectError as exc:
raise OracleConnectionError(str(exc), source="oracle", entity=path) from exc
except httpx.TimeoutException as exc:
raise OracleTimeoutError(str(exc), source="oracle", entity=path) from exc
return self._handle_response(response, path)
def _handle_response(self, response: httpx.Response, path: str) -> Any:
if response.status_code == 404:
raise OracleNotFoundError(
f"Not found: {path}",
source="oracle",
entity=path,
context={"status_code": 404},
)
if response.status_code >= 500:
raise OracleServerError(
f"Server error {response.status_code}: {path}",
source="oracle",
entity=path,
context={"status_code": response.status_code},
)
if response.status_code >= 400:
raise OracleClientError(
f"Client error {response.status_code}: {path}",
source="oracle",
entity=path,
context={"status_code": response.status_code},
)
return response.json()
async def health_check(self) -> bool:
try:
data = await self.get("/health")
return isinstance(data, dict) and data.get("status") == "ok"
except Exception:
return False
def make_oracle_client() -> OracleClient:
from libs.common.config import get_settings
s = get_settings()
return OracleClient(base_url=s.stock_oracle_url, timeout=float(s.stock_oracle_timeout))