Files
dzentra_bot/app/tools/dzengi_probe/rest_probe.py

195 lines
5.8 KiB
Python

# app/tools/dzengi_probe/rest_probe.py
from __future__ import annotations
import json
from dataclasses import dataclass
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from app.tools.dzengi_probe.config import DzengiProbeConfig
from app.tools.dzengi_probe.response_store import save_json
@dataclass(frozen=True)
class RestProbeResult:
endpoint: str
path: str
output_file: str
ok: bool
error: str | None = None
class DzengiRestProbe:
def __init__(self, config: DzengiProbeConfig) -> None:
self.config = config
def run_all(self) -> list[RestProbeResult]:
return [
self.probe_time(),
self.probe_exchange_info(),
self.probe_ticker24hr(),
self.probe_depth(),
self.probe_klines(),
self.probe_agg_trades(),
]
def probe_time(self) -> RestProbeResult:
return self._get_and_save(
endpoint="time",
path=f"/api/{self.config.api_version}/time",
params=None,
output_file="rest/time/response.json",
)
def probe_exchange_info(self) -> RestProbeResult:
return self._get_and_save(
endpoint="exchangeInfo",
path=f"/api/{self.config.api_version}/exchangeInfo",
params=None,
output_file="rest/exchangeInfo/all.json",
)
def probe_ticker24hr(self) -> RestProbeResult:
return self._get_and_save(
endpoint="ticker24hr",
path=f"/api/{self.config.api_version}/ticker/24hr",
params={"symbol": self.config.symbol},
output_file=f"rest/ticker24hr/{self.config.symbol_file_name}.json",
)
def probe_depth(self) -> RestProbeResult:
return self._get_and_save(
endpoint="depth",
path=f"/api/{self.config.api_version}/depth",
params={
"symbol": self.config.symbol,
"limit": self.config.depth_limit,
},
output_file=f"rest/depth/{self.config.symbol_file_name}.json",
)
def probe_klines(self) -> RestProbeResult:
return self._get_and_save(
endpoint="klines",
path=f"/api/{self.config.api_version}/klines",
params={
"symbol": self.config.symbol,
"interval": self.config.interval,
"limit": "10",
},
output_file=(
f"rest/klines/"
f"{self.config.symbol_file_name}_{self.config.interval}.json"
),
)
def probe_agg_trades(self) -> RestProbeResult:
return self._get_and_save(
endpoint="aggTrades",
path=f"/api/{self.config.api_version}/aggTrades",
params={
"symbol": self.config.symbol,
"limit": self.config.agg_trades_limit,
},
output_file=f"rest/aggTrades/{self.config.symbol_file_name}.json",
)
def _get_and_save(
self,
*,
endpoint: str,
path: str,
params: dict[str, str] | None,
output_file: str,
) -> RestProbeResult:
url = self._build_url(path, params)
request = Request(
url=url,
method="GET",
headers={
"Accept": "application/json",
"User-Agent": "dzentra-dzengi-probe/1.0",
},
)
try:
with urlopen(request, timeout=self.config.timeout_seconds) as response:
status = getattr(response, "status", 200)
body = response.read().decode("utf-8")
if status != 200:
return RestProbeResult(
endpoint=endpoint,
path=path,
output_file=output_file,
ok=False,
error=f"Unexpected HTTP status: {status}",
)
payload = json.loads(body)
output_path = self.config.output_dir / output_file
save_json(output_path, payload)
return RestProbeResult(
endpoint=endpoint,
path=path,
output_file=str(output_path),
ok=True,
)
except HTTPError as exc:
error_body = ""
try:
error_body = exc.read().decode("utf-8")
except Exception:
pass
message = f"HTTP {exc.code}: {exc.reason}"
if error_body:
message += f" | body: {error_body}"
return RestProbeResult(
endpoint=endpoint,
path=path,
output_file=output_file,
ok=False,
error=message,
)
except URLError as exc:
return RestProbeResult(
endpoint=endpoint,
path=path,
output_file=output_file,
ok=False,
error=f"Network error: {exc.reason}",
)
except TimeoutError:
return RestProbeResult(
endpoint=endpoint,
path=path,
output_file=output_file,
ok=False,
error="Timeout while calling Dzengi API.",
)
except json.JSONDecodeError as exc:
return RestProbeResult(
endpoint=endpoint,
path=path,
output_file=output_file,
ok=False,
error=f"Non-JSON response: {exc}",
)
def _build_url(
self,
path: str,
params: dict[str, str] | None,
) -> str:
query = f"?{urlencode(params)}" if params else ""
return f"{self.config.base_url}{path}{query}"