195 lines
5.8 KiB
Python
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}" |