Nav apraksta

iris.py 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. from typing import Any
  2. import httpx
  3. class IrisAdapter:
  4. def __init__(self, base_url: str, api_key: str) -> None:
  5. self.base_url = base_url.rstrip("/")
  6. self.api_key = api_key
  7. def _headers(self) -> dict[str, str]:
  8. return {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}
  9. async def create_case(self, payload: dict[str, Any]) -> dict[str, Any]:
  10. headers = self._headers()
  11. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  12. v2_url = f"{self.base_url}/api/v2/cases"
  13. response = await client.post(v2_url, json=payload, headers=headers)
  14. if response.status_code == 404:
  15. legacy_url = f"{self.base_url}/manage/cases/add"
  16. response = await client.post(legacy_url, json=payload, headers=headers)
  17. active_url = legacy_url
  18. else:
  19. active_url = v2_url
  20. try:
  21. response.raise_for_status()
  22. except httpx.HTTPStatusError as exc:
  23. detail = response.text.strip()
  24. raise RuntimeError(
  25. f"IRIS returned {response.status_code} for {active_url}. Response: {detail}"
  26. ) from exc
  27. return response.json() if response.content else {"status_code": response.status_code}
  28. async def update_case(self, case_id: str, payload: dict[str, Any]) -> dict[str, Any]:
  29. headers = self._headers()
  30. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  31. v2_url = f"{self.base_url}/api/v2/cases/{case_id}"
  32. response = await client.put(v2_url, json=payload, headers=headers)
  33. if response.status_code == 404:
  34. legacy_url = f"{self.base_url}/manage/cases/update/{case_id}"
  35. response = await client.post(legacy_url, json=payload, headers=headers)
  36. active_url = legacy_url
  37. else:
  38. active_url = v2_url
  39. try:
  40. response.raise_for_status()
  41. except httpx.HTTPStatusError as exc:
  42. detail = response.text.strip()
  43. raise RuntimeError(
  44. f"IRIS returned {response.status_code} for {active_url}. Response: {detail}"
  45. ) from exc
  46. return response.json() if response.content else {"status_code": response.status_code}
  47. async def whoami(self) -> dict[str, Any]:
  48. headers = self._headers()
  49. url = f"{self.base_url}/user/whoami"
  50. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  51. response = await client.get(url, headers=headers)
  52. response.raise_for_status()
  53. return response.json() if response.content else {"status_code": response.status_code}
  54. async def list_cases(self, limit: int = 50, offset: int = 0) -> dict[str, Any]:
  55. headers = self._headers()
  56. safe_limit = max(1, limit)
  57. safe_offset = max(0, offset)
  58. page = (safe_offset // safe_limit) + 1
  59. v2_url = f"{self.base_url}/api/v2/cases"
  60. params = {"page": page, "per_page": safe_limit}
  61. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  62. response = await client.get(v2_url, params=params, headers=headers)
  63. active_url = v2_url
  64. # Older IRIS releases may not expose /api/v2/cases for listing.
  65. if response.status_code == 404:
  66. legacy_url = f"{self.base_url}/manage/cases/list"
  67. legacy_attempts: list[tuple[str, dict[str, Any] | None, dict[str, Any] | None]] = [
  68. ("post", None, {"start": safe_offset, "length": safe_limit, "draw": 1}),
  69. ("post", None, {"page": (safe_offset // safe_limit) + 1, "per_page": safe_limit}),
  70. ("get", {"limit": safe_limit, "offset": safe_offset}, None),
  71. ]
  72. for method, legacy_params, legacy_payload in legacy_attempts:
  73. if method == "post":
  74. response = await client.post(
  75. legacy_url,
  76. params=legacy_params,
  77. json=legacy_payload,
  78. headers=headers,
  79. )
  80. else:
  81. response = await client.get(
  82. legacy_url,
  83. params=legacy_params,
  84. headers=headers,
  85. )
  86. active_url = legacy_url
  87. if response.status_code not in {404, 405, 415}:
  88. break
  89. try:
  90. response.raise_for_status()
  91. except httpx.HTTPStatusError as exc:
  92. detail = response.text.strip()
  93. raise RuntimeError(
  94. f"IRIS returned {response.status_code} for {active_url}. Response: {detail}"
  95. ) from exc
  96. if not response.content:
  97. return {"status_code": response.status_code}
  98. result = response.json()
  99. # IRIS v2 response: { "total": int, "data": [...], "last_page": int, ... }
  100. if isinstance(result, dict) and isinstance(result.get("total"), int) and isinstance(result.get("data"), list):
  101. return result # pass v2 response through directly
  102. if isinstance(result, dict) and isinstance(result.get("data"), list):
  103. total = result.get("recordsTotal")
  104. if not isinstance(total, int):
  105. total = len(result["data"])
  106. return {
  107. "items": result["data"],
  108. "total": total,
  109. "limit": safe_limit,
  110. "offset": safe_offset,
  111. "source": "legacy_manage_cases_list",
  112. "raw": result,
  113. }
  114. return result
  115. async def create_alert(self, payload: dict[str, Any]) -> dict[str, Any]:
  116. headers = self._headers()
  117. url = f"{self.base_url}/alerts/add"
  118. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  119. response = await client.post(url, json=payload, headers=headers)
  120. try:
  121. response.raise_for_status()
  122. except httpx.HTTPStatusError as exc:
  123. detail = response.text.strip()
  124. raise RuntimeError(
  125. f"IRIS returned {response.status_code} for {url}. Response: {detail}"
  126. ) from exc
  127. return response.json() if response.content else {"status_code": response.status_code}
  128. async def get_case(self, case_id: int) -> dict[str, Any]:
  129. headers = self._headers()
  130. url = f"{self.base_url}/api/v2/cases/{case_id}"
  131. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  132. response = await client.get(url, headers=headers)
  133. try:
  134. response.raise_for_status()
  135. except httpx.HTTPStatusError as exc:
  136. detail = response.text.strip()
  137. raise RuntimeError(
  138. f"IRIS returned {response.status_code} for {url}. Response: {detail}"
  139. ) from exc
  140. return response.json() if response.content else {}
  141. async def get_alert(self, alert_id: int) -> dict[str, Any]:
  142. headers = self._headers()
  143. url = f"{self.base_url}/alerts/{alert_id}"
  144. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  145. response = await client.get(url, headers=headers)
  146. try:
  147. response.raise_for_status()
  148. except httpx.HTTPStatusError as exc:
  149. detail = response.text.strip()
  150. raise RuntimeError(
  151. f"IRIS returned {response.status_code} for {url}. Response: {detail}"
  152. ) from exc
  153. result = response.json() if response.content else {}
  154. # Legacy endpoint returns {"status": "success", "data": {...}}
  155. return result.get("data", result)
  156. async def list_alerts(
  157. self,
  158. page: int = 1,
  159. per_page: int = 50,
  160. sort_by: str = "alert_id",
  161. sort_dir: str = "desc",
  162. filter_title: str | None = None,
  163. filter_owner_id: int | None = None,
  164. ) -> dict[str, Any]:
  165. headers = self._headers()
  166. url = f"{self.base_url}/api/v2/alerts"
  167. params: dict[str, Any] = {
  168. "page": max(1, page),
  169. "per_page": max(1, per_page),
  170. "sort": f"{sort_by}:{sort_dir}",
  171. }
  172. if filter_title:
  173. params["alert_title"] = filter_title
  174. if filter_owner_id:
  175. params["alert_owner_id"] = filter_owner_id
  176. async with httpx.AsyncClient(verify=False, timeout=30.0) as client:
  177. response = await client.get(url, params=params, headers=headers)
  178. try:
  179. response.raise_for_status()
  180. except httpx.HTTPStatusError as exc:
  181. detail = response.text.strip()
  182. raise RuntimeError(
  183. f"IRIS returned {response.status_code} for {url}. Response: {detail}"
  184. ) from exc
  185. return response.json() if response.content else {"status_code": response.status_code}
  186. async def assign_alert(self, alert_id: int, owner_id: int) -> dict[str, Any]:
  187. headers = self._headers()
  188. url = f"{self.base_url}/api/v2/alerts/{alert_id}"
  189. payload = {"alert_owner_id": owner_id}
  190. async with httpx.AsyncClient(verify=False, timeout=20.0) as client:
  191. response = await client.put(url, json=payload, headers=headers)
  192. try:
  193. response.raise_for_status()
  194. except httpx.HTTPStatusError as exc:
  195. detail = response.text.strip()
  196. raise RuntimeError(
  197. f"IRIS returned {response.status_code} for {url}. Response: {detail}"
  198. ) from exc
  199. return response.json() if response.content else {"status_code": response.status_code}