Files
freepik/freepik_cli/api/client.py
T

110 lines
3.8 KiB
Python
Raw Normal View History

2026-04-08 10:56:45 +02:00
"""Freepik HTTP client with authentication, error handling, and download support."""
from __future__ import annotations
from typing import Any, Optional
import httpx
from freepik_cli import __version__
BASE_URL = "https://api.freepik.com"
DEFAULT_TIMEOUT = 60.0
class FreepikAPIError(Exception):
"""Raised when the Freepik API returns an error response."""
def __init__(self, message: str, status_code: Optional[int] = None, raw: Optional[dict] = None):
super().__init__(message)
self.status_code = status_code
self.raw = raw or {}
@classmethod
def from_response(cls, response: httpx.Response) -> "FreepikAPIError":
try:
body = response.json()
except Exception:
body = {}
message = (
body.get("message")
or body.get("error", {}).get("message")
or body.get("errors", [{}])[0].get("message")
or f"HTTP {response.status_code}"
)
hints = {
401: "Check your API key — set FREEPIK_API_KEY or use --api-key.",
403: "Your plan may not support this feature. Check your Freepik subscription.",
422: "Invalid request parameters. Check the options you provided.",
429: "Rate limit exceeded. Please wait before retrying.",
}
hint = hints.get(response.status_code)
if hint:
message = f"{message}\n\nHint: {hint}"
return cls(message, status_code=response.status_code, raw=body)
class FreepikClient:
"""Thin synchronous HTTP wrapper around the Freepik API."""
def __init__(
self,
api_key: str,
base_url: str = BASE_URL,
timeout: float = DEFAULT_TIMEOUT,
) -> None:
self._client = httpx.Client(
base_url=base_url,
headers={
"x-freepik-api-key": api_key,
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": f"freepik-cli/{__version__}",
},
timeout=httpx.Timeout(timeout),
)
def post(self, path: str, json: dict[str, Any]) -> dict[str, Any]:
try:
response = self._client.post(path, json=json)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as exc:
raise FreepikAPIError.from_response(exc.response) from exc
except httpx.RequestError as exc:
raise FreepikAPIError(f"Network error: {exc}") from exc
def post_multipart(self, path: str, data: dict[str, Any], files: dict[str, Any]) -> dict[str, Any]:
"""POST with multipart/form-data (for file uploads)."""
headers = {k: v for k, v in self._client.headers.items() if k.lower() != "content-type"}
try:
response = self._client.post(path, data=data, files=files, headers=headers)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as exc:
raise FreepikAPIError.from_response(exc.response) from exc
except httpx.RequestError as exc:
raise FreepikAPIError(f"Network error: {exc}") from exc
def get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
try:
response = self._client.get(path, params=params)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as exc:
raise FreepikAPIError.from_response(exc.response) from exc
except httpx.RequestError as exc:
raise FreepikAPIError(f"Network error: {exc}") from exc
def __enter__(self) -> "FreepikClient":
return self
def __exit__(self, *args: Any) -> None:
self._client.close()
def close(self) -> None:
self._client.close()