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.
105 lines
3.6 KiB
Python
105 lines
3.6 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) -> Any:
|
|
client = self._ensure_client()
|
|
try:
|
|
response = await client.post(path, json=json)
|
|
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))
|