Advertisement
joejoinerr

Python: Base API client

Mar 25th, 2022 (edited)
262
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.27 KB | None | 0 0
  1. import os
  2. from collections.abc import Mapping
  3. from types import TracebackType
  4. from typing import Any, Self
  5.  
  6. import httpx
  7. from loguru import logger
  8.  
  9.  
  10. def _get_api_key_env(key_name: str) -> str:
  11.     try:
  12.         return os.environ[key_name]
  13.     except KeyError as e:
  14.         raise MissingKeyError(key_name=key_name) from e
  15.  
  16.  
  17. class APIClient:
  18.     def __init__(
  19.         self,
  20.         *,
  21.         http_client: httpx.AsyncClient | None = None,
  22.         api_key: str | None = None,
  23.         base_url: str = "https://api.example.com/",
  24.         retries: int = 3,
  25.         timeout: float = 10.0,
  26.         **kwargs: Any,
  27.     ) -> None:
  28.         if http_client is not None:
  29.             self._session = http_client
  30.         else:
  31.             self.api_key = api_key or _get_api_key_env("API_KEY")  # api key loading under else
  32.             transport = httpx.AsyncHTTPTransport(retries=retries)
  33.             session_args = dict(
  34.                 base_url=base_url,
  35.                 transport=transport,
  36.                 timeout=timeout,
  37.                 params={"key": self.api_key},
  38.             )
  39.             self._session = httpx.AsyncClient(**session_args, **kwargs)
  40.  
  41.     async def _request(
  42.         self,
  43.         method: str,
  44.         path: str,
  45.         params: Mapping[str, Any] | None = None,
  46.         data: Mapping[str, Any] | None = None,
  47.     ) -> str:
  48.         try:
  49.             response = await self._session.request(
  50.                 method=method, url=path, params=params, json=data
  51.             )
  52.             response.raise_for_status()
  53.         except httpx.RequestError as e:
  54.             logger.error("Problem with API request: {}", str(e))
  55.             raise RequestError(str(e)) from e
  56.         except httpx.HTTPStatusError as e:
  57.             res_body = e.response.json()
  58.             err_msg = res_body["error"]["message"]
  59.             logger.error("Failed to fetch API data: {}", err_msg)
  60.             raise ResponseError(
  61.                 status_code=e.response.status_code, message=err_msg
  62.             ) from e
  63.  
  64.         return response.text
  65.  
  66.     async def close(self) -> None:
  67.         """Closes the client."""
  68.         await self._session.aclose()
  69.  
  70.     async def __aenter__(self) -> Self:
  71.         """Opens the client."""
  72.         return self
  73.  
  74.     async def __aexit__(
  75.         self,
  76.         exc_type: type[BaseException] | None,
  77.         exc_val: BaseException | None,
  78.         exc_tb: TracebackType | None,
  79.     ) -> None:
  80.         """Closes the client."""
  81.         await self.close()
  82.  
  83.  
  84. class APIClientError(Exception):
  85.     pass
  86.  
  87.  
  88. class MissingKeyError(APIClientError):
  89.     def __init__(self, key_name: str) -> None:
  90.         super().__init__(
  91.             f"API key is not set. Please set the `{key_name}` environment variable or "
  92.             f"pass the key directly to the constructor."
  93.         )
  94.  
  95.  
  96. class RequestError(APIClientError):
  97.     def __init__(self, message: str) -> None:
  98.         self.message = message
  99.         super().__init__(f"Problem with API request: {self.message}")
  100.  
  101.  
  102. class ResponseError(APIClientError):
  103.     def __init__(self, status_code: int, message: str) -> None:
  104.         self.status_code = status_code
  105.         self.message = message
  106.         super().__init__(
  107.             f"API responded with an error: {self.status_code} - {self.message}"
  108.         )
  109.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement