clients.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. from __future__ import annotations
  2. import os
  3. from dataclasses import dataclass
  4. from typing import Any, Dict, Optional
  5. import requests
  6. from services.analysis_service import AnalysisService as AnalysisApi
  7. @dataclass(frozen=True)
  8. class ApiResult:
  9. ok: bool
  10. status_code: int
  11. data: Any = None
  12. error: Optional[str] = None
  13. class ApiClient:
  14. def __init__(self, base_url: str, timeout_sec: float = 5.0) -> None:
  15. self.base_url = base_url.rstrip("/")
  16. self.timeout_sec = float(timeout_sec)
  17. self.session = requests.Session()
  18. def _url(self, path: str) -> str:
  19. if not path.startswith("/"):
  20. path = "/" + path
  21. return f"{self.base_url}{path}"
  22. def _request(self, method: str, path: str, **kwargs: Any) -> ApiResult:
  23. url = self._url(path)
  24. try:
  25. r = self.session.request(method=method, url=url, timeout=self.timeout_sec, **kwargs)
  26. sc = r.status_code
  27. r.raise_for_status()
  28. return ApiResult(ok=True, status_code=sc, data=(r.json() if r.content else None))
  29. except Exception as e:
  30. sc = getattr(getattr(e, "response", None), "status_code", 0)
  31. return ApiResult(ok=False, status_code=sc or 0, error=str(e))
  32. def get_json(self, path: str, params: Optional[Dict[str, Any]] = None) -> ApiResult:
  33. return self._request("GET", path, params=params)
  34. def post_json(self, path: str, payload: Dict[str, Any]) -> ApiResult:
  35. return self._request("POST", path, json=payload)
  36. def post_file(self, path: str, file_path: str, field_name: str = "file") -> ApiResult:
  37. try:
  38. with open(file_path, "rb") as f:
  39. files = {field_name: (os.path.basename(file_path), f)}
  40. return self._request("POST", path, files=files)
  41. except Exception as e:
  42. sc = getattr(getattr(e, "response", None), "status_code", 0)
  43. return ApiResult(ok=False, status_code=sc or 0, error=str(e))
  44. class LoggedApiClient(ApiClient):
  45. def _full_url(self, endpoint: str) -> str:
  46. endpoint = endpoint if endpoint.startswith("/") else f"/{endpoint}"
  47. return f"{self.base_url}{endpoint}"
  48. def get_json(self, endpoint: str, params: Optional[dict] = None) -> ApiResult:
  49. print(f"[HTTP][GET] {self._full_url(endpoint)} params={params}")
  50. return super().get_json(endpoint, params=params)
  51. def post_json(self, endpoint: str, payload: dict) -> ApiResult:
  52. print(f"[HTTP][POST] {self._full_url(endpoint)} payload={payload}")
  53. return super().post_json(endpoint, payload)
  54. def post_file(self, endpoint: str, file_path: str, field_name: str = "file") -> ApiResult:
  55. if os.path.exists(file_path):
  56. size = os.path.getsize(file_path)
  57. else:
  58. size = -1
  59. print(f"[HTTP][UPLOAD] {self._full_url(endpoint)} file={file_path} size={size}")
  60. return super().post_file(endpoint, file_path, field_name)
  61. class LoggedAnalysisApi:
  62. def __init__(self, base_url: str, timeout: float):
  63. self.base_url = base_url.rstrip("/")
  64. self.timeout = timeout
  65. def _api(self) -> AnalysisApi:
  66. return AnalysisApi(self.base_url, timeout=self.timeout)
  67. def upload(self, file_path: str) -> str:
  68. print(f"[ANALYSIS][POST] {self.base_url}/upload file={file_path}")
  69. return self._api().upload(file_path)
  70. def filter(self, session_id: str, dt: float, center: float, lo: float, hi: float, low: float) -> dict:
  71. print(f"[ANALYSIS][POST] {self.base_url}/filter sid={session_id}")
  72. return self._api().filter(session_id, dt, center, lo, hi, low)
  73. def fft(
  74. self,
  75. session_id: str,
  76. c1: Optional[int],
  77. c2: Optional[int],
  78. c3: Optional[int],
  79. c4: Optional[int],
  80. ) -> dict:
  81. print(f"[ANALYSIS][POST] {self.base_url}/fft sid={session_id} c=({c1},{c2},{c3},{c4})")
  82. return self._api().fft(session_id, c1=c1, c2=c2, c3=c3, c4=c4)
  83. def result(self, session_id: str) -> dict:
  84. print(f"[ANALYSIS][GET] {self.base_url}/result/{session_id}")
  85. return self._api().result(session_id)
  86. def export_plots(self, session_id: str) -> dict:
  87. print(f"[ANALYSIS][GET] {self.base_url}/export/{session_id}")
  88. return self._api().export_plots(session_id)
  89. def plot_raw(self, session_id: str) -> dict:
  90. print(f"[ANALYSIS][GET] {self.base_url}/plot-raw/{session_id}")
  91. return self._api().plot_raw(session_id)
  92. def export_raw_data(self, session_id: str) -> dict:
  93. print(f"[ANALYSIS][GET] {self.base_url}/export-raw-data/{session_id}")
  94. return self._api().export_raw_data(session_id)
  95. def export_filter_data(
  96. self,
  97. session_id: str,
  98. center_freq: float,
  99. lower_freq: float,
  100. higher_freq: float,
  101. low_freq: float,
  102. ) -> dict:
  103. print(f"[ANALYSIS][GET] {self.base_url}/export-filter-data/{session_id}")
  104. return self._api().export_filter_data(
  105. session_id,
  106. center_freq=center_freq,
  107. lower_freq=lower_freq,
  108. higher_freq=higher_freq,
  109. low_freq=low_freq,
  110. )
  111. def export_decdem_data(self, session_id: str) -> dict:
  112. print(f"[ANALYSIS][GET] {self.base_url}/export-decdem-data/{session_id}")
  113. return self._api().export_decdem_data(session_id)
  114. def export_position_freq(self, session_id: str) -> dict:
  115. print(f"[ANALYSIS][GET] {self.base_url}/export-position-freq/{session_id}")
  116. return self._api().export_position_freq(session_id)
  117. def export_fwhm(self, session_id: str) -> dict:
  118. print(f"[ANALYSIS][GET] {self.base_url}/export-fwhm/{session_id}")
  119. return self._api().export_fwhm(session_id)
  120. def export_max_amplitude_freq(self, session_id: str) -> dict:
  121. print(f"[ANALYSIS][GET] {self.base_url}/export-max-amplitude-freq/{session_id}")
  122. return self._api().export_max_amplitude_freq(session_id)
  123. def download_bytes(self, rel_url: str) -> bytes:
  124. url = f"{self.base_url.rstrip('/')}/{rel_url.lstrip('/')}"
  125. print(f"[ANALYSIS][GET-BYTES] {url}")
  126. r = requests.get(url, timeout=self.timeout)
  127. r.raise_for_status()
  128. return r.content
  129. @dataclass
  130. class AnalysisState:
  131. base_url: str
  132. session_id: str = ""
  133. last_uploaded_path: str = ""