- Cast sharpen parameter to int before API call (fixes HTTP 400 validation error) - Add precision upscaler endpoint to task status polling list (was missing) - Add 2s initial delay after task creation to allow server propagation - Handle 404 errors during polling gracefully with retry logic Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
236 lines
7.3 KiB
Python
236 lines
7.3 KiB
Python
"""
|
|
Freepik API Client
|
|
Handles all HTTP communication with Freepik API
|
|
"""
|
|
|
|
import requests
|
|
import time
|
|
import json
|
|
from typing import Dict, Any, Optional, List
|
|
from requests.adapters import HTTPAdapter
|
|
from urllib3.util.retry import Retry
|
|
|
|
|
|
class FreepikAPIClient:
|
|
"""Base client for Freepik API communication"""
|
|
|
|
BASE_URL = "https://api.freepik.com"
|
|
API_VERSION = "v1"
|
|
|
|
def __init__(self, api_key: Optional[str] = None):
|
|
self.api_key = api_key
|
|
self.session = self._create_session()
|
|
|
|
def _create_session(self) -> requests.Session:
|
|
"""Create requests session with retry logic"""
|
|
session = requests.Session()
|
|
|
|
# Retry strategy
|
|
retry_strategy = Retry(
|
|
total=3,
|
|
backoff_factor=1,
|
|
status_forcelist=[429, 500, 502, 503, 504],
|
|
allowed_methods=["GET", "POST"]
|
|
)
|
|
|
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
session.mount("http://", adapter)
|
|
session.mount("https://", adapter)
|
|
|
|
return session
|
|
|
|
def set_api_key(self, api_key: str):
|
|
"""Set or update API key"""
|
|
self.api_key = api_key
|
|
|
|
def _get_headers(self) -> Dict[str, str]:
|
|
"""Generate request headers with API key"""
|
|
if not self.api_key:
|
|
raise ValueError("API key not set. Use set_api_key() method.")
|
|
|
|
return {
|
|
"x-freepik-api-key": self.api_key,
|
|
"Content-Type": "application/json",
|
|
"Accept": "application/json"
|
|
}
|
|
|
|
def _build_url(self, endpoint: str) -> str:
|
|
"""Build full API URL from endpoint"""
|
|
# Remove leading slash if present
|
|
endpoint = endpoint.lstrip('/')
|
|
return f"{self.BASE_URL}/{endpoint}"
|
|
|
|
def post(self, endpoint: str, data: Dict[str, Any],
|
|
files: Optional[Dict] = None) -> Dict[str, Any]:
|
|
"""
|
|
POST request to Freepik API
|
|
|
|
Args:
|
|
endpoint: API endpoint (e.g., '/v1/ai/mystic')
|
|
data: Request payload
|
|
files: Optional files for multipart upload
|
|
|
|
Returns:
|
|
Response JSON as dict
|
|
"""
|
|
url = self._build_url(endpoint)
|
|
headers = self._get_headers()
|
|
|
|
try:
|
|
if files:
|
|
# For file uploads, remove Content-Type header (requests will set it)
|
|
headers.pop('Content-Type', None)
|
|
response = self.session.post(
|
|
url,
|
|
data=data,
|
|
files=files,
|
|
headers=headers,
|
|
timeout=30
|
|
)
|
|
else:
|
|
response = self.session.post(
|
|
url,
|
|
json=data,
|
|
headers=headers,
|
|
timeout=30
|
|
)
|
|
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
except requests.exceptions.HTTPError as e:
|
|
error_msg = f"HTTP Error {e.response.status_code}"
|
|
try:
|
|
error_detail = e.response.json()
|
|
error_msg += f": {error_detail}"
|
|
except:
|
|
error_msg += f": {e.response.text}"
|
|
raise Exception(error_msg)
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
raise Exception(f"Request failed: {str(e)}")
|
|
|
|
def get(self, endpoint: str, params: Optional[Dict] = None) -> Dict[str, Any]:
|
|
"""
|
|
GET request to Freepik API
|
|
|
|
Args:
|
|
endpoint: API endpoint
|
|
params: Optional query parameters
|
|
|
|
Returns:
|
|
Response JSON as dict
|
|
"""
|
|
url = self._build_url(endpoint)
|
|
headers = self._get_headers()
|
|
|
|
try:
|
|
response = self.session.get(
|
|
url,
|
|
params=params,
|
|
headers=headers,
|
|
timeout=30
|
|
)
|
|
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
except requests.exceptions.HTTPError as e:
|
|
error_msg = f"HTTP Error {e.response.status_code}"
|
|
try:
|
|
error_detail = e.response.json()
|
|
error_msg += f": {error_detail}"
|
|
except:
|
|
error_msg += f": {e.response.text}"
|
|
raise Exception(error_msg)
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
raise Exception(f"Request failed: {str(e)}")
|
|
|
|
def create_task(self, endpoint: str, params: Dict[str, Any],
|
|
files: Optional[Dict] = None) -> str:
|
|
"""
|
|
Create an async task and return task_id
|
|
|
|
Args:
|
|
endpoint: API endpoint
|
|
params: Task parameters
|
|
files: Optional files for upload
|
|
|
|
Returns:
|
|
task_id string
|
|
"""
|
|
response = self.post(endpoint, params, files)
|
|
print(f"🔍 DEBUG - Create task full response: {response}")
|
|
|
|
# Handle different response formats
|
|
if 'data' in response:
|
|
if 'task_id' in response['data']:
|
|
return response['data']['task_id']
|
|
elif 'id' in response['data']:
|
|
return response['data']['id']
|
|
elif 'task_id' in response:
|
|
return response['task_id']
|
|
elif 'id' in response:
|
|
return response['id']
|
|
else:
|
|
raise Exception(f"Could not extract task_id from response: {response}")
|
|
|
|
def get_task_status(self, task_id: str) -> Dict[str, Any]:
|
|
"""Get status of an async task"""
|
|
endpoints_to_try = [
|
|
f"/v1/ai/mystic/{task_id}",
|
|
f"/v1/ai/image-upscaler/{task_id}",
|
|
f"/v1/ai/image-upscaler-precision/{task_id}",
|
|
f"/v1/ai/task/{task_id}",
|
|
f"/v1/task/{task_id}"
|
|
]
|
|
|
|
last_error = None
|
|
for endpoint in endpoints_to_try:
|
|
try:
|
|
response = self.get(endpoint)
|
|
print(f"🔍 DEBUG - Raw API response from {endpoint}: {response}")
|
|
return response
|
|
except Exception as e:
|
|
last_error = e
|
|
continue
|
|
|
|
raise last_error
|
|
|
|
def download_image(self, url: str) -> bytes:
|
|
"""
|
|
Download image from URL
|
|
|
|
Args:
|
|
url: Image URL
|
|
|
|
Returns:
|
|
Image bytes
|
|
"""
|
|
try:
|
|
response = self.session.get(url, timeout=60)
|
|
response.raise_for_status()
|
|
return response.content
|
|
except Exception as e:
|
|
raise Exception(f"Failed to download image: {str(e)}")
|
|
|
|
def get_balance(self) -> Dict[str, Any]:
|
|
"""Get current credit balance (if API supports it)"""
|
|
# This endpoint may vary, adjust as needed
|
|
try:
|
|
return self.get("/v1/user/balance")
|
|
except:
|
|
return {"credits": "unknown", "error": "Balance endpoint not available"}
|
|
|
|
|
|
# Convenience function for testing
|
|
def test_connection(api_key: str) -> bool:
|
|
"""Test if API key is valid"""
|
|
try:
|
|
client = FreepikAPIClient(api_key)
|
|
# Try a simple GET request
|
|
client.get("/v1/ai/loras") # Public endpoint
|
|
return True
|
|
except:
|
|
return False |