import httpx from httpx_retries import Retry, RetryTransport from komgapi.errors import KomgaError, ResultErrror from typing import Any, Union from limit import limit # type:ignore import loguru import sys import json log = loguru.logger log.remove() log.add("logs/komga_api.log", rotation="1 week", retention="1 month") log.add(sys.stdout, level="INFO") class BaseAPI: def __init__( self, username: str, password: str, url: str, timeout: int = 20, api_version: int = 1, ) -> None: self._username = username self._password = password self.url = url + f"api/v{api_version}/" self.timeout = timeout self.headers = { "Content-Type": "application/json", "Accept": "application/json", } def setParams(self, locals: dict[Any, Any]) -> dict[Any, Any]: return { param_name: param for param_name, param in locals.items() if param is not None and param_name not in ["self", "series_idurl", "query", "url"] } def test_connection(self): try: with httpx.Client(timeout=self.timeout) as client: client.get(self.url, headers=self.headers) return True except httpx.RequestError: return False def overwriteVersion(self, version: int): self.url = self.url.replace("api/v1/", f"api/v{version}/") return self @limit(1, 1) def getRequest(self, url: str, params: Union[dict[Any, Any], None] = None) -> Any: if params is None: params = {} try: with httpx.Client( timeout=self.timeout, auth=(self._username, self._password), transport=RetryTransport(retry=Retry(total=5, backoff_factor=0.5)), ) as client: response = client.get(url, params=params, headers=self.headers) if response.status_code != 200: return self.getRequest(url, params) return response.json() except httpx.ConnectError as e: raise KomgaError(f"Connection Error: {e}") from e except httpx.TimeoutException as e: raise KomgaError(f"Timeout Error: {e}") from e def postRequest( self, url: str, data: Union[dict[Any, Any], None] = None, body: Union[dict[Any, Any], None] = None, ): if data is None: data = {} try: with httpx.Client( timeout=self.timeout, auth=(self._username, self._password), transport=RetryTransport(retry=Retry(total=5, backoff_factor=0.5)) ) as client: response = client.post( url, params=data, json=body if body is not None else {}, headers=self.headers, ) log.debug( "POST request to {} with data: {}, json: {}", url, json.dumps(data), json.dumps(body), ) response.raise_for_status() status_code = response.status_code if status_code == 202: return None elif status_code == 200: return response.json() else: raise ResultErrror(f"Result Error: {response.content}") except httpx.ConnectError as e: raise KomgaError(f"Connection Error: {e}") from e except httpx.TimeoutException as e: raise KomgaError(f"Timeout Error: {e}") from e def patchRequest(self, url: str, data: Union[dict[Any, Any], None] = None): """Send PATCH request to API endpoint. Args: url (str): API endpoint URL data (Union[dict[Any, Any], None]): Data to send in request body Returns: dict: JSON response from server Raises: KomgaError: For connection/timeout errors ResultError: For invalid responses """ if data is None: data = {} try: log.debug("PATCH request to {} with data: {}", url, json.dumps(data)) with httpx.Client( timeout=self.timeout, auth=(self._username, self._password), headers=self.headers, ) as client: response = client.patch(url, json=data) response.raise_for_status() if response.status_code == 204: return None return response.json() if response.content else None except httpx.ConnectError as e: log.error("Connection error during PATCH to {}: {}", url, str(e)) raise KomgaError(f"Connection Error: {e}") from e except httpx.TimeoutException as e: log.error("Timeout during PATCH to {}: {}", url, str(e)) raise KomgaError(f"Timeout Error: {e}") from e except httpx.HTTPStatusError as e: log.error("HTTP error during PATCH to {}: {}", url, e.response.text) raise ResultErrror(f"Result Error: {e.response.text}") from e def deleteRequest(self, url: str): try: with httpx.Client( timeout=self.timeout, auth=(self._username, self._password) ) as client: response = client.delete(url, headers=self.headers) response.raise_for_status() return response.json() except httpx.ConnectError as e: raise KomgaError(f"Connection Error: {e}") from e except httpx.TimeoutException as e: raise KomgaError(f"Timeout Error: {e}") from e @classmethod def from_env(cls): import os return cls( os.environ["KOMGA_USERNAME"], os.environ["KOMGA_PASSWORD"], os.environ["KOMGA_URL"], )