Repository URL to install this package:
|
Version:
5.0.3 ▾
|
"""Client API module."""
from typing import Any, Literal
import requests
from requests import HTTPError, Response, Session
from requests.auth import AuthBase
from sincpro_framework import DataTransferObject, logger
from sincpro_payments_sdk.exceptions import SincproExternalServiceError, SincproHTTPError
class ApiResponse(DataTransferObject):
"""API response model."""
raw_response: dict | None = None
class ClientAPI:
"""Client API class."""
def __init__(self, session: Session | None = None, auth: AuthBase | None = None) -> None:
"""Initialize the client API."""
self._session = session
self._auth = auth
@property
def use_session(self) -> bool:
"""Set the session."""
return bool(self._session)
@property
def base_url(self) -> str:
"""Get the base URL for the API."""
raise NotImplementedError("You must implement the base_url property")
def execute_request(
self,
resource: str,
method: Literal["GET", "POST", "PUT", "DELETE"] = "GET",
params=None,
headers=None,
data=None,
timeout=None,
auth=None,
) -> Response: # pylint: disable=too-many-arguments
"""Low-level function for API calls.
It wraps the requests library for managing sessions and custom Exceptions
Args:
resource (str): Path of the resource.
method (str, optional): HTTP method to execute. Defaults to "GET".
params (Dict[str, Any], optional): Query parameters to include in the URL. Defaults to None.
headers (Dict[str, str], optional): HTTP method to execute. Defaults to None.
data (Any, optional): Payload to send in the body. Defaults to None.
timeout (int, optional): Amount of seconds to wait for a timeout and raise an exception
auth (AuthBase, optional): Authentication object to use in the request. Defaults to None.
Raises:
SincproHTTPError: HTTP error (4xx/5xx) from the external service.
SincproExternalServiceError: Network-level error (connection, timeout, redirects).
Returns:
Response: Model for the HTTP response in requests
"""
url = f"{self.base_url}{resource}"
logger.debug(f"Requesting:[{method}] {url}", with_auth=bool(auth or self._auth))
kwargs: dict[str, Any] = {
"url": url,
"method": method,
}
if data:
kwargs["json"] = data
if headers:
kwargs["headers"] = headers
if params:
kwargs["params"] = params
logger.debug(f"Params: {params}")
if timeout:
kwargs["timeout"] = timeout
if self._auth:
kwargs["auth"] = self._auth
if auth:
kwargs["auth"] = auth
if method != "GET":
logger.debug(f"Body: {data}")
try:
if self.use_session:
assert self._session is not None
response = self._session.request(**kwargs)
else:
response = requests.request(**kwargs)
except requests.ConnectionError as exc:
raise SincproExternalServiceError(f"Connection error to {url}") from exc
except requests.Timeout as exc:
raise SincproExternalServiceError(f"Request timed out for {url}") from exc
except requests.TooManyRedirects as exc:
raise SincproExternalServiceError(f"Too many redirects for {url}") from exc
try:
response.raise_for_status()
except HTTPError as error:
http_code = error.response.status_code
reason = error.response.reason
content = getattr(error.response, "content", None)
text = getattr(error.response, "text", None)
raise SincproHTTPError(
str(error),
http_code=http_code,
reason=reason,
content=content,
text=text,
) from error
return response
@staticmethod
def safe_parse_json(response: Response) -> dict:
"""Parse JSON response, wrapping decode errors in SincproExternalServiceError."""
try:
return response.json()
except (ValueError, TypeError) as exc:
raise SincproExternalServiceError(
f"Failed to parse JSON response (HTTP {response.status_code})"
) from exc